#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use super::ast::{BashExpr, BashStmt};
use super::parser::{BashParser, ParseError};
#[test]
fn test_expect_error_missing_then() {
let input = "if [ 1 = 1 ]; echo missing_then; fi";
let result = BashParser::new(input).and_then(|mut p| p.parse());
assert!(
result.is_err(),
"should error on missing 'then': {:?}",
result
);
if let Err(ParseError::UnexpectedToken { expected, .. }) = result {
assert!(
expected.contains("then") || expected.contains("'then'"),
"expected hint about 'then', got: {expected}"
);
}
}
#[test]
fn test_expect_error_missing_do_in_while() {
let input = "while true; echo no_do; done";
let result = BashParser::new(input).and_then(|mut p| p.parse());
assert!(result.is_err(), "should error on missing 'do'");
}
#[test]
fn test_expect_error_missing_fi() {
let input = "if true; then\n echo hi\n";
let result = BashParser::new(input).and_then(|mut p| p.parse());
assert!(result.is_err(), "should error on missing 'fi'");
}
#[test]
fn test_expect_error_missing_done() {
let input = "for x in 1 2 3; do\n echo $x\n";
let result = BashParser::new(input).and_then(|mut p| p.parse());
assert!(result.is_err(), "should error on missing 'done'");
}
#[test]
fn test_expect_error_missing_esac() {
let input = "case $x in\n a) echo a ;;\n";
let result = BashParser::new(input).and_then(|mut p| p.parse());
assert!(result.is_err(), "should error on missing 'esac'");
}
#[test]
fn test_expect_success_then_present() {
let input = "if [ 1 = 1 ]; then\n echo ok\nfi";
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_tokens_adjacent_assignment_no_space() {
let input = "FOO=bar";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert_eq!(ast.statements.len(), 1);
assert!(
matches!(ast.statements[0], BashStmt::Assignment { .. }),
"no-space assignment should be BashStmt::Assignment"
);
}
#[test]
fn test_tokens_adjacent_variable_value() {
let input = "X=hello_world";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(matches!(ast.statements[0], BashStmt::Assignment { .. }));
if let BashStmt::Assignment { name, value, .. } = &ast.statements[0] {
assert_eq!(name, "X");
if let BashExpr::Literal(v) = value {
assert_eq!(v, "hello_world");
}
}
}
#[test]
fn test_tokens_adjacent_empty_assignment() {
let input = "EMPTY=";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(!ast.statements.is_empty());
}
#[test]
fn test_tokens_adjacent_multiple_assignments() {
let input = "A=1\nB=2\nC=3";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert_eq!(ast.statements.len(), 3);
assert!(ast
.statements
.iter()
.all(|s| matches!(s, BashStmt::Assignment { .. })));
}
#[test]
fn test_skip_condition_redirects_heredoc_in_while() {
let input = "while read line; do\n echo $line\ndone <<EOF\nhello\nEOF";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_skip_condition_redirects_bare_output_redirect() {
let input = "for x in 1 2 3; do\n echo $x\ndone > /tmp/out.txt";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_skip_condition_redirects_append_redirect() {
let input = "for x in a b; do\n echo $x\ndone >> /tmp/log.txt";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_skip_condition_redirects_input_redirect() {
let input = "while read line; do\n echo $line\ndone < /tmp/input.txt";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_skip_condition_redirects_fd_prefixed() {
let input = "for x in 1 2; do\n echo $x\ndone 2>/dev/null";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_skip_condition_redirects_fd_duplication() {
let input = "while true; do\n echo hi\n break\ndone 2>&1";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_skip_condition_redirects_herestring() {
let input = "while read line; do\n echo $line\ndone <<< \"hello world\"";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_skip_compound_redirects_on_fi() {
let input = "if true; then\n echo hi\nfi > /tmp/out";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_parse_coproc_basic() {
let input = "coproc cat /dev/stdin";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_parse_select_statement() {
let input = "select opt in a b c; do\n echo $opt\ndone";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_parse_process_substitution_arg() {
let input = "diff <(sort a.txt) <(sort b.txt)";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_parse_pipeline_rhs_while() {
let input = "cat file.txt | while read line; do echo $line; done";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_parse_pipeline_rhs_for() {
let input = "ls | for x in 1 2; do echo $x; done";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_parse_pipeline_rhs_if() {
let input = "echo hello | if true; then cat; fi";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_parse_pipeline_rhs_brace_group() {
let input = "cat file | { sort; uniq; }";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_parse_pipeline_rhs_subshell() {
let input = "cat file | (sort | uniq)";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_parse_background_operator() {
let input = "sleep 100 &\necho done";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(!ast.statements.is_empty());
}
#[test]
fn test_parse_or_list() {
let input = "command_a || command_b";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(ast
.statements
.iter()
.any(|s| matches!(s, BashStmt::OrList { .. })));
}
#[test]
fn test_parse_and_list() {
let input = "mkdir -p /tmp/foo && echo created";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(ast
.statements
.iter()
.any(|s| matches!(s, BashStmt::AndList { .. })));
}
#[test]
fn test_parse_block_background_separator() {
let input = "{ cmd1 & cmd2 & cmd3; }";
let result = BashParser::new(input).and_then(|mut p| p.parse());
let _ = result;
}
#[test]
fn test_parse_comment() {
let input = "# this is a comment\necho hello";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(ast
.statements
.iter()
.any(|s| matches!(s, BashStmt::Comment { .. })));
}
#[test]
fn test_parse_error_line_number() {
let input = "if true; then\n echo hi\n echo there\n";
let result = BashParser::new(input).and_then(|mut p| p.parse());
assert!(result.is_err());
if let Err(e) = result {
let _ = e.line(); }
}
#[test]
fn test_parse_error_column_from_lexer() {
let input = r#"echo "unclosed"#;
let result = BashParser::new(input);
assert!(result.is_err());
if let Err(e) = result {
let _ = e.column();
let _ = e.line();
}
}
#[test]
include!("parser_coverage_tests_tests_format_parse.rs");