Port rest of test_parser

Most of this is already ported into the "test_parser" test.
This commit is contained in:
Johannes Altmanninger 2023-12-09 19:05:42 +01:00
parent 09b7f3892f
commit c74cc71e26
2 changed files with 40 additions and 253 deletions

View file

@ -1,4 +1,5 @@
use crate::ast::{Ast, List, Node};
use crate::expand::ExpandFlags;
use crate::io::{IoBufferfill, IoChain};
use crate::parse_constants::{ParseTreeFlags, ParserTestErrorBits};
use crate::parse_util::{parse_util_detect_errors, parse_util_detect_errors_in_argument};
@ -290,6 +291,45 @@ add_test!("test_parser", || {
);
});
add_test!("test_eval_recursion_detection", || {
// Ensure that we don't crash on infinite self recursion and mutual recursion. These must use
// the principal parser because we cannot yet execute jobs on other parsers.
let parser = Parser::principal_parser().shared();
parser.eval(
L!("function recursive ; recursive ; end ; recursive; "),
&IoChain::new(),
);
parser.eval(
L!(concat!(
"function recursive1 ; recursive2 ; end ; ",
"function recursive2 ; recursive1 ; end ; recursive1; ",
)),
&IoChain::new(),
);
});
add_test!("test_eval_empty_function_name", || {
let parser = Parser::principal_parser().shared();
parser.eval(
L!("function '' ; echo fail; exit 42 ; end ; ''"),
&IoChain::new(),
);
});
add_test!("test_expand_argument_list", || {
let parser = Parser::principal_parser().shared();
let comps: Vec<WString> = Parser::expand_argument_list(
L!("alpha 'beta gamma' delta"),
ExpandFlags::default(),
&parser.context(),
)
.into_iter()
.map(|c| c.completion)
.collect();
assert_eq!(comps, &[L!("alpha"), L!("beta gamma"), L!("delta"),]);
});
fn test_1_cancellation(src: &wstr) {
let filler = IoBufferfill::create().unwrap();
let delay = Duration::from_millis(500);

View file

@ -524,258 +524,6 @@ static void test_pthread() {
do_test(val == 5);
}
static parser_test_error_bits_t detect_argument_errors(const wcstring &src) {
using namespace ast;
auto ast = ast_parse_argument_list(src, parse_flag_none);
if (ast->errored()) {
return PARSER_TEST_ERROR;
}
const ast::argument_t *first_arg =
ast->top()->as_freestanding_argument_list().arguments().at(0);
if (!first_arg) {
err(L"Failed to parse an argument");
return 0;
}
return parse_util_detect_errors_in_argument(*first_arg, *first_arg->source(src));
}
/// Test the parser.
// todo!("port this");
static void test_parser() {
say(L"Testing parser");
auto detect_errors = [](const wcstring &s) {
return parse_util_detect_errors(s, nullptr, true /* accept incomplete */);
};
say(L"Testing block nesting");
if (!detect_errors(L"if; end")) {
err(L"Incomplete if statement undetected");
}
if (!detect_errors(L"if test; echo")) {
err(L"Missing end undetected");
}
if (!detect_errors(L"if test; end; end")) {
err(L"Unbalanced end undetected");
}
say(L"Testing detection of invalid use of builtin commands");
if (!detect_errors(L"case foo")) {
err(L"'case' command outside of block context undetected");
}
if (!detect_errors(L"switch ggg; if true; case foo;end;end")) {
err(L"'case' command outside of switch block context undetected");
}
if (!detect_errors(L"else")) {
err(L"'else' command outside of conditional block context undetected");
}
if (!detect_errors(L"else if")) {
err(L"'else if' command outside of conditional block context undetected");
}
if (!detect_errors(L"if false; else if; end")) {
err(L"'else if' missing command undetected");
}
if (!detect_errors(L"break")) {
err(L"'break' command outside of loop block context undetected");
}
if (detect_errors(L"break --help")) {
err(L"'break --help' incorrectly marked as error");
}
if (!detect_errors(L"while false ; function foo ; break ; end ; end ")) {
err(L"'break' command inside function allowed to break from loop outside it");
}
if (!detect_errors(L"exec ls|less") || !detect_errors(L"echo|return")) {
err(L"Invalid pipe command undetected");
}
if (detect_errors(L"for i in foo ; switch $i ; case blah ; break; end; end ")) {
err(L"'break' command inside switch falsely reported as error");
}
if (detect_errors(L"or cat | cat") || detect_errors(L"and cat | cat")) {
err(L"boolean command at beginning of pipeline falsely reported as error");
}
if (!detect_errors(L"cat | and cat")) {
err(L"'and' command in pipeline not reported as error");
}
if (!detect_errors(L"cat | or cat")) {
err(L"'or' command in pipeline not reported as error");
}
if (!detect_errors(L"cat | exec") || !detect_errors(L"exec | cat")) {
err(L"'exec' command in pipeline not reported as error");
}
if (!detect_errors(L"begin ; end arg")) {
err(L"argument to 'end' not reported as error");
}
if (!detect_errors(L"switch foo ; end arg")) {
err(L"argument to 'end' not reported as error");
}
if (!detect_errors(L"if true; else if false ; end arg")) {
err(L"argument to 'end' not reported as error");
}
if (!detect_errors(L"if true; else ; end arg")) {
err(L"argument to 'end' not reported as error");
}
if (detect_errors(L"begin ; end 2> /dev/null")) {
err(L"redirection after 'end' wrongly reported as error");
}
if (detect_errors(L"true | ") != PARSER_TEST_INCOMPLETE) {
err(L"unterminated pipe not reported properly");
}
if (detect_errors(L"echo (\nfoo\n bar") != PARSER_TEST_INCOMPLETE) {
err(L"unterminated multiline subshell not reported properly");
}
if (detect_errors(L"begin ; true ; end | ") != PARSER_TEST_INCOMPLETE) {
err(L"unterminated pipe not reported properly");
}
if (detect_errors(L" | true ") != PARSER_TEST_ERROR) {
err(L"leading pipe not reported properly");
}
if (detect_errors(L"true | # comment") != PARSER_TEST_INCOMPLETE) {
err(L"comment after pipe not reported as incomplete");
}
if (detect_errors(L"true | # comment \n false ")) {
err(L"comment and newline after pipe wrongly reported as error");
}
if (detect_errors(L"true | ; false ") != PARSER_TEST_ERROR) {
err(L"semicolon after pipe not detected as error");
}
if (detect_argument_errors(L"foo")) {
err(L"simple argument reported as error");
}
if (detect_argument_errors(L"''")) {
err(L"Empty string reported as error");
}
if (!(detect_argument_errors(L"foo$$") & PARSER_TEST_ERROR)) {
err(L"Bad variable expansion not reported as error");
}
if (!(detect_argument_errors(L"foo$@") & PARSER_TEST_ERROR)) {
err(L"Bad variable expansion not reported as error");
}
// Within command substitutions, we should be able to detect everything that
// parse_util_detect_errors can detect.
if (!(detect_argument_errors(L"foo(cat | or cat)") & PARSER_TEST_ERROR)) {
err(L"Bad command substitution not reported as error");
}
if (!detect_errors(L"false & ; and cat")) {
err(L"'and' command after background not reported as error");
}
if (!detect_errors(L"true & ; or cat")) {
err(L"'or' command after background not reported as error");
}
if (detect_errors(L"true & ; not cat")) {
err(L"'not' command after background falsely reported as error");
}
if (!detect_errors(L"if true & ; end")) {
err(L"backgrounded 'if' conditional not reported as error");
}
if (!detect_errors(L"if false; else if true & ; end")) {
err(L"backgrounded 'else if' conditional not reported as error");
}
if (!detect_errors(L"while true & ; end")) {
err(L"backgrounded 'while' conditional not reported as error");
}
if (!detect_errors(L"true | || false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!detect_errors(L"|| false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!detect_errors(L"&& false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!detect_errors(L"true ; && false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!detect_errors(L"true ; || false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!detect_errors(L"true || && false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!detect_errors(L"true && || false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!detect_errors(L"true && && false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (detect_errors(L"true && ") != PARSER_TEST_INCOMPLETE) {
err(L"unterminated conjunction not reported properly");
}
if (detect_errors(L"true && \n true")) {
err(L"newline after && reported as error");
}
if (detect_errors(L"true || \n") != PARSER_TEST_INCOMPLETE) {
err(L"unterminated conjunction not reported properly");
}
say(L"Testing basic evaluation");
// Ensure that we don't crash on infinite self recursion and mutual recursion. These must use
// the principal parser because we cannot yet execute jobs on other parsers.
auto parser = parser_principal_parser()->deref().shared();
say(L"Testing recursion detection");
parser->deref().eval(L"function recursive ; recursive ; end ; recursive; ", *new_io_chain());
parser->deref().eval(
L"function recursive1 ; recursive2 ; end ; "
L"function recursive2 ; recursive1 ; end ; recursive1; ",
*new_io_chain());
say(L"Testing empty function name");
parser->deref().eval(L"function '' ; echo fail; exit 42 ; end ; ''", *new_io_chain());
say(L"Testing eval_args");
wcstring_list_ffi_t comps;
parser_expand_argument_list_ffi(L"alpha 'beta gamma' delta", expand_flags_t{},
*parser_context(parser->deref()), comps);
do_test(comps.size() == 3);
do_test(comps.at(0) == L"alpha");
do_test(comps.at(1) == L"beta gamma");
do_test(comps.at(2) == L"delta");
}
static void test_const_strlen() {
do_test(const_strlen("") == 0);
do_test(const_strlen(L"") == 0);
@ -2109,7 +1857,6 @@ static const test_t s_tests[]{
{TEST_GROUP("convert_nulls"), test_convert_nulls},
{TEST_GROUP("iothread"), test_iothread},
{TEST_GROUP("pthread"), test_pthread},
{TEST_GROUP("parser"), test_parser},
{TEST_GROUP("lru"), test_lru},
{TEST_GROUP("wcstod"), test_wcstod},
{TEST_GROUP("word_motion"), test_word_motion},