mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 21:33:09 +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::ast::{Ast, List, Node};
|
||||||
|
use crate::expand::ExpandFlags;
|
||||||
use crate::io::{IoBufferfill, IoChain};
|
use crate::io::{IoBufferfill, IoChain};
|
||||||
use crate::parse_constants::{ParseTreeFlags, ParserTestErrorBits};
|
use crate::parse_constants::{ParseTreeFlags, ParserTestErrorBits};
|
||||||
use crate::parse_util::{parse_util_detect_errors, parse_util_detect_errors_in_argument};
|
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) {
|
fn test_1_cancellation(src: &wstr) {
|
||||||
let filler = IoBufferfill::create().unwrap();
|
let filler = IoBufferfill::create().unwrap();
|
||||||
let delay = Duration::from_millis(500);
|
let delay = Duration::from_millis(500);
|
||||||
|
|
|
@ -524,258 +524,6 @@ static void test_pthread() {
|
||||||
do_test(val == 5);
|
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() {
|
static void test_const_strlen() {
|
||||||
do_test(const_strlen("") == 0);
|
do_test(const_strlen("") == 0);
|
||||||
do_test(const_strlen(L"") == 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("convert_nulls"), test_convert_nulls},
|
||||||
{TEST_GROUP("iothread"), test_iothread},
|
{TEST_GROUP("iothread"), test_iothread},
|
||||||
{TEST_GROUP("pthread"), test_pthread},
|
{TEST_GROUP("pthread"), test_pthread},
|
||||||
{TEST_GROUP("parser"), test_parser},
|
|
||||||
{TEST_GROUP("lru"), test_lru},
|
{TEST_GROUP("lru"), test_lru},
|
||||||
{TEST_GROUP("wcstod"), test_wcstod},
|
{TEST_GROUP("wcstod"), test_wcstod},
|
||||||
{TEST_GROUP("word_motion"), test_word_motion},
|
{TEST_GROUP("word_motion"), test_word_motion},
|
||||||
|
|
Loading…
Reference in a new issue