mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
Port rest of test_parser
Most of this is already ported into the "test_parser" test.
This commit is contained in:
parent
09b7f3892f
commit
c74cc71e26
2 changed files with 40 additions and 253 deletions
|
@ -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);
|
||||
|
|
|
@ -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},
|
||||
|
|
Loading…
Reference in a new issue