#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
#![allow(non_snake_case)]
use bashrs::bash_parser::{BashParser, BashStmt};
fn parse_ok(source: &str) -> Vec<BashStmt> {
let mut parser = BashParser::new(source).expect("lexer should succeed");
let ast = parser.parse().expect("parse should succeed");
ast.statements
}
fn has_stmt<F>(stmts: &[BashStmt], pred: F) -> bool
where
F: Fn(&BashStmt) -> bool,
{
stmts.iter().any(&pred)
}
#[test]
fn falsify_PARSE_001_echo_command() {
let stmts = parse_ok("echo \"hello world\"");
assert!(
has_stmt(
&stmts,
|s| matches!(s, BashStmt::Command { name, .. } if name == "echo")
),
"F-PARSE-001: echo must parse to Command {{ name: 'echo' }}"
);
}
#[test]
fn falsify_PARSE_002_assignment() {
let stmts = parse_ok("x=42");
assert!(
has_stmt(
&stmts,
|s| matches!(s, BashStmt::Assignment { name, .. } if name == "x")
),
"F-PARSE-002: x=42 must parse to Assignment {{ name: 'x' }}"
);
}
#[test]
fn falsify_PARSE_003_if_else() {
let stmts = parse_ok("if [ \"$x\" = \"1\" ]; then echo yes; else echo no; fi");
assert!(
has_stmt(&stmts, |s| matches!(
s,
BashStmt::If {
else_block: Some(_),
..
}
)),
"F-PARSE-003: if/else must parse to If with else_block"
);
}
#[test]
fn falsify_PARSE_004_for_loop() {
let stmts = parse_ok("for f in *.txt; do echo \"$f\"; done");
assert!(
has_stmt(
&stmts,
|s| matches!(s, BashStmt::For { variable, .. } if variable == "f")
),
"F-PARSE-004: for loop must parse to For {{ variable: 'f' }}"
);
}
#[test]
fn falsify_PARSE_005_function_def() {
let stmts = parse_ok("greet() { echo hello; }");
assert!(
has_stmt(
&stmts,
|s| matches!(s, BashStmt::Function { name, .. } if name == "greet")
),
"F-PARSE-005: function must parse to Function {{ name: 'greet' }}"
);
}
#[test]
fn falsify_PARSE_006_pipeline() {
let stmts = parse_ok("ls -la | grep foo | wc -l");
assert!(
has_stmt(&stmts, |s| matches!(s, BashStmt::Pipeline { .. })),
"F-PARSE-006: pipeline must parse to Pipeline node, got: {:?}",
stmts.iter().map(std::mem::discriminant).collect::<Vec<_>>()
);
}
#[test]
fn falsify_PARSE_007_determinism() {
let input = "x=1\nif [ \"$x\" ]; then echo yes; fi";
let stmts1 = parse_ok(input);
let stmts2 = parse_ok(input);
assert_eq!(
stmts1, stmts2,
"F-PARSE-007: parse(input) must equal parse(input)"
);
}
#[test]
fn falsify_PARSE_007_determinism_complex() {
let input = "#!/bin/bash\nfor f in *.sh; do\n echo \"$f\"\ndone\ngreet() { echo hi; }";
let stmts1 = parse_ok(input);
let stmts2 = parse_ok(input);
assert_eq!(
stmts1, stmts2,
"F-PARSE-007: complex parse must be deterministic"
);
}
#[test]
fn falsify_PARSE_008_empty_input() {
let stmts = parse_ok("");
assert!(
stmts.is_empty(),
"F-PARSE-008: empty input must produce empty AST, got {} statements",
stmts.len()
);
}
#[test]
fn falsify_PARSE_009_comment_only() {
let mut parser = BashParser::new("# this is a comment").expect("lexer ok");
let result = parser.parse();
assert!(
result.is_ok(),
"F-PARSE-009: comment-only input must parse successfully"
);
}
#[test]
fn falsify_PARSE_010_statement_count() {
let stmts = parse_ok("x=1\ny=2\necho $x $y");
assert_eq!(
stmts.len(),
3,
"F-PARSE-010: 3 statements must produce 3 AST nodes, got {}",
stmts.len()
);
}
#[test]
fn falsify_PARSE_011_while_loop() {
let stmts = parse_ok("while true; do sleep 1; done");
assert!(
has_stmt(&stmts, |s| matches!(s, BashStmt::While { .. })),
"F-PARSE-011: while loop must parse to While node"
);
}
#[test]
fn falsify_PARSE_012_case_statement() {
let stmts = parse_ok("case \"$1\" in start) echo go;; stop) echo halt;; esac");
let has_case = has_stmt(&stmts, |s| {
if let BashStmt::Case { arms, .. } = s {
arms.len() == 2
} else {
false
}
});
assert!(has_case, "F-PARSE-012: case must parse to Case with 2 arms");
}