use super::*; fn parse(x: &[u8]) -> Ast { do_parse(x).unwrap() } const TIMEOUT_MS: u64 = 100; macro_rules! parse_test { ($l:expr, $r:expr $(,)?) => {{ let (tx, rx) = std::sync::mpsc::channel(); std::thread::spawn(move || { #[allow(unreachable_code, unused_variables)] let result = std::panic::catch_unwind(|| { let l = $l; let r = $r; if l != r { let mut left = Vec::new(); l.cdisplay(&mut left).unwrap(); let mut right = Vec::new(); r.cdisplay(&mut right).unwrap(); let left = String::from_utf8_lossy(&left); let right = String::from_utf8_lossy(&right); if left != right { panic!("parse equality error\nleft: {left}\nright: {right}") } } }); let _ = tx.send(result); }); match rx.recv_timeout(std::time::Duration::from_millis(TIMEOUT_MS)) { Ok(Ok(())) => (), Ok(Err(e)) => std::panic::resume_unwind(e), Err(_) => panic!("test timed out after {TIMEOUT_MS} ms"), } }}; } #[test] fn command_interp() { parse_test!( parse(br#""$(echo echo)""#), pipes([cmd([str([cmdp(pipes([cmd([ estr(b"echo"), estr(b"echo"), ])]))])])]), ) } #[test] fn string_concat() { parse_test!( parse(br#" foo'bar'"baz" "#), pipes([cmd([estr(b"foobarbaz")])]), ); } #[test] fn simple_string() { parse_test!(parse(b"foo"), pipes([cmd([estr(b"foo")])])); } #[test] fn simple_var() { parse_test!(parse(b"$foo"), pipes([cmd([str([var(b"foo")])])])); } #[test] fn ls_pipe_cat() { parse_test!( parse(b"ls | cat"), pipes([cmd([estr(b"ls")]), cmd([estr(b"cat")])]), ); } #[test] fn ls_pipe_cat_nospace() { parse_test!( parse(b"ls|cat"), pipes([cmd([estr(b"ls")]), cmd([estr(b"cat")])]), ); } #[test] fn unclosed_single_quote() { assert!(do_parse(b"x'").is_err()) } #[test] fn unclosed_double_quote() { assert!(do_parse(b"x\"").is_err()) } #[test] fn tilde() { parse_test!( parse(b"echo ~"), pipes([cmd([estr(b"echo"), str([var(b"HOME")])])]), ); } #[test] fn tilde2() { parse_test!( parse(b"echo ~/foo/bar"), pipes([cmd([ estr(b"echo"), str([var(b"HOME"), plain(b"/foo/bar")]), ])]), ); } #[test] fn tilde3() { parse_test!( parse(b"echo ~ "), pipes([cmd([estr(b"echo"), str([var(b"HOME")])])]), ); } #[test] fn tilde4() { parse_test!( parse(b"echo ~'x'"), pipes([cmd([estr(b"echo"), estr(b"~x")])]), ); } #[test] fn tilde5() { parse_test!( parse(b"echo ~$FOO"), pipes([cmd([estr(b"echo"), str([plain(b"~"), var(b"FOO")])])]), ); } #[test] fn tilde6() { parse_test!( parse(b"git rebase -i HEAD~10"), pipes([cmd([ estr(b"git"), estr(b"rebase"), estr(b"-i"), estr(b"HEAD~10"), ])]), ); } #[test] fn set_variable_in_fun() { parse_test!( parse(b"fun setter { set x = 1 }"), decl(estr(b"setter"), block([assign(estr(b"x"), estr(b"1"))])), ); } #[test] fn variable_with_defaults() { parse_test!( parse(b"${x:-y}"), pipes([cmd([str([var_default(b"x", estr(b"y"))])])]), ); } #[test] fn escape_newline() { parse_test!(parse(b"\"\\n\""), pipes([cmd([estr(b"\n")])])); } #[test] fn escape_carriage_return() { parse_test!(parse(b"\"\\r\""), pipes([cmd([estr(b"\r")])])); } #[test] fn escape_tab() { parse_test!(parse(b"\"\\t\""), pipes([cmd([estr(b"\t")])])); } #[test] fn escape_hex_1() { parse_test!(parse(b"\\x41"), pipes([cmd([estr(b"A")])])); } #[test] fn escape_hex_2() { parse_test!(parse(b"\\x0a"), pipes([cmd([estr(b"\n")])])); } #[test] fn pipe_on_new_line() { parse_test!( parse(b"cat file \n | cat"), pipes([cmd([estr(b"cat"), estr(b"file")]), cmd([estr(b"cat")])]), ); } #[test] fn semicolon() { parse_test!( parse(b"fun f { x ; y }"), decl( estr(b"f"), block([pipes([cmd([estr(b"x")])]), pipes([cmd([estr(b"y")])])]), ), ); } #[test] fn newline_separates_commands() { parse_test!( parse(b"fun f { x \n y }"), decl( estr(b"f"), block([pipes([cmd([estr(b"x")])]), pipes([cmd([estr(b"y")])])]), ), ); } #[test] fn newline_does_not_separate_pipes() { parse_test!( parse(b"fun f { x \n| y }"), decl( estr(b"f"), block([pipes([cmd([estr(b"x")]), cmd([estr(b"y")])])]), ), ); } #[test] fn simple_if() { parse_test!(parse(b"if cond { x }"), todo!()); } #[test] fn if_else() { parse_test!(parse(b"if cond { x } else { y }"), todo!()); } #[test] fn simple_while() { parse_test!(parse(b"while cond { x }"), todo!()); }