diff options
Diffstat (limited to 'fish_tests.cpp')
-rw-r--r-- | fish_tests.cpp | 821 |
1 files changed, 774 insertions, 47 deletions
diff --git a/fish_tests.cpp b/fish_tests.cpp index e7047c4b..af65b70a 100644 --- a/fish_tests.cpp +++ b/fish_tests.cpp @@ -60,8 +60,38 @@ #include "postfork.h" #include "signal.h" #include "highlight.h" +#include "parse_tree.h" #include "parse_util.h" +static const char * const * s_arguments; +static int s_test_run_count = 0; + +/* Indicate if we should test the given function. Either we test everything (all arguments) or we run only tests that have a prefix in s_arguments */ +static bool should_test_function(const char *func_name) +{ + /* No args, test everything */ + bool result = false; + if (! s_arguments || ! s_arguments[0]) + { + result = true; + } + else + { + for (size_t i=0; s_arguments[i] != NULL; i++) + { + if (! strncmp(func_name, s_arguments[i], strlen(s_arguments[i]))) + { + /* Prefix match */ + result = true; + break; + } + } + } + if (result) + s_test_run_count++; + return result; +} + /** The number of tests to run */ @@ -410,6 +440,18 @@ static void test_tok() } } } + + /* Test redirection_type_for_string */ + if (redirection_type_for_string(L"<") != TOK_REDIRECT_IN) err(L"redirection_type_for_string failed on line %ld", (long)__LINE__); + if (redirection_type_for_string(L"^") != TOK_REDIRECT_OUT) err(L"redirection_type_for_string failed on line %ld", (long)__LINE__); + if (redirection_type_for_string(L">") != TOK_REDIRECT_OUT) err(L"redirection_type_for_string failed on line %ld", (long)__LINE__); + if (redirection_type_for_string(L"2>") != TOK_REDIRECT_OUT) err(L"redirection_type_for_string failed on line %ld", (long)__LINE__); + if (redirection_type_for_string(L">>") != TOK_REDIRECT_APPEND) err(L"redirection_type_for_string failed on line %ld", (long)__LINE__); + if (redirection_type_for_string(L"2>>") != TOK_REDIRECT_APPEND) err(L"redirection_type_for_string failed on line %ld", (long)__LINE__); + if (redirection_type_for_string(L"2>?") != TOK_REDIRECT_NOCLOB) err(L"redirection_type_for_string failed on line %ld", (long)__LINE__); + if (redirection_type_for_string(L"9999999999999999>?") != TOK_NONE) err(L"redirection_type_for_string failed on line %ld", (long)__LINE__); + if (redirection_type_for_string(L"2>&3") != TOK_REDIRECT_FD) err(L"redirection_type_for_string failed on line %ld", (long)__LINE__); + if (redirection_type_for_string(L"2>|") != TOK_NONE) err(L"redirection_type_for_string failed on line %ld", (long)__LINE__); } static int test_fork_helper(void *unused) @@ -542,56 +584,68 @@ static void test_parser() parser_t parser(PARSER_TYPE_GENERAL, true); - say(L"Testing null input to parser"); - if (!parser.test(NULL)) - { - err(L"Null input to parser.test undetected"); - } - say(L"Testing block nesting"); - if (!parser.test(L"if; end")) + if (!parse_util_detect_errors(L"if; end")) { err(L"Incomplete if statement undetected"); } - if (!parser.test(L"if test; echo")) + if (!parse_util_detect_errors(L"if test; echo")) { err(L"Missing end undetected"); } - if (!parser.test(L"if test; end; end")) + if (!parse_util_detect_errors(L"if test; end; end")) { err(L"Unbalanced end undetected"); } say(L"Testing detection of invalid use of builtin commands"); - if (!parser.test(L"case foo")) + if (!parse_util_detect_errors(L"case foo")) { err(L"'case' command outside of block context undetected"); } - if (!parser.test(L"switch ggg; if true; case foo;end;end")) + if (!parse_util_detect_errors(L"switch ggg; if true; case foo;end;end")) { err(L"'case' command outside of switch block context undetected"); } - if (!parser.test(L"else")) + if (!parse_util_detect_errors(L"else")) { err(L"'else' command outside of conditional block context undetected"); } - if (!parser.test(L"else if")) + if (!parse_util_detect_errors(L"else if")) { err(L"'else if' command outside of conditional block context undetected"); } - if (!parser.test(L"if false; else if; end")) + if (!parse_util_detect_errors(L"if false; else if; end")) { err(L"'else if' missing command undetected"); } - if (!parser.test(L"break")) + if (!parse_util_detect_errors(L"break")) { err(L"'break' command outside of loop block context undetected"); } - if (!parser.test(L"exec ls|less") || !parser.test(L"echo|return")) + + if (parse_util_detect_errors(L"break --help")) + { + err(L"'break --help' incorrectly marked as error"); + } + + if (! parse_util_detect_errors(L"while false ; function foo ; break ; end ; end ")) + { + err(L"'break' command inside function allowed to break from loop outside it"); + } + + + if (!parse_util_detect_errors(L"exec ls|less") || !parse_util_detect_errors(L"echo|return")) { err(L"Invalid pipe command undetected"); } + + if (parse_util_detect_errors(L"for i in foo ; switch $i ; case blah ; break; end; end ")) + { + err(L"'break' command inside switch falsely reported as error"); + } + say(L"Testing basic evaluation"); #if 0 @@ -607,6 +661,154 @@ static void test_parser() } } +static void test_indents() +{ + say(L"Testing indents"); + + // Here are the components of our source and the indents we expect those to be + struct indent_component_t { + const wchar_t *txt; + int indent; + }; + + const indent_component_t components1[] = + { + {L"if foo", 0}, + {L"end", 0}, + {NULL, -1} + }; + + const indent_component_t components2[] = + { + {L"if foo", 0}, + {L"", 1}, //trailing newline! + {NULL, -1} + }; + + const indent_component_t components3[] = + { + {L"if foo", 0}, + {L"foo", 1}, + {L"end", 0}, //trailing newline! + {NULL, -1} + }; + + const indent_component_t components4[] = + { + {L"if foo", 0}, + {L"if bar", 1}, + {L"end", 1}, + {L"end", 0}, + {L"", 0}, + {NULL, -1} + }; + + const indent_component_t components5[] = + { + {L"if foo", 0}, + {L"if bar", 1}, + {L"", 2}, + {NULL, -1} + }; + + const indent_component_t components6[] = + { + {L"begin", 0}, + {L"foo", 1}, + {L"", 1}, + {NULL, -1} + }; + + const indent_component_t components7[] = + { + {L"begin; end", 0}, + {L"foo", 0}, + {L"", 0}, + {NULL, -1} + }; + + const indent_component_t components8[] = + { + {L"if foo", 0}, + {L"if bar", 1}, + {L"baz", 2}, + {L"end", 1}, + {L"", 1}, + {NULL, -1} + }; + + const indent_component_t components9[] = + { + {L"switch foo", 0}, + {L"", 1}, + {NULL, -1} + }; + + const indent_component_t components10[] = + { + {L"switch foo", 0}, + {L"case bar", 1}, + {L"case baz", 1}, + {L"quux", 2}, + {L"", 2}, + {NULL, -1} + }; + + const indent_component_t components11[] = + { + {L"switch foo", 0}, + {L"cas", 1}, //parse error indentation handling + {NULL, -1} + }; + + + + const indent_component_t *tests[] = {components1, components2, components3, components4, components5, components6, components7, components8, components9, components10, components11}; + for (size_t which = 0; which < sizeof tests / sizeof *tests; which++) + { + const indent_component_t *components = tests[which]; + // Count how many we have + size_t component_count = 0; + while (components[component_count].txt != NULL) + { + component_count++; + } + + // Generate the expected indents + wcstring text; + std::vector<int> expected_indents; + for (size_t i=0; i < component_count; i++) + { + if (i > 0) + { + text.push_back(L'\n'); + expected_indents.push_back(components[i].indent); + } + text.append(components[i].txt); + expected_indents.resize(text.size(), components[i].indent); + } + assert(expected_indents.size() == text.size()); + + // Compute the indents + std::vector<int> indents = parse_util_compute_indents(text); + + if (expected_indents.size() != indents.size()) + { + err(L"Indent vector has wrong size! Expected %lu, actual %lu", expected_indents.size(), indents.size()); + } + assert(expected_indents.size() == indents.size()); + for (size_t i=0; i < text.size(); i++) + { + if (expected_indents.at(i) != indents.at(i)) + { + err(L"Wrong indent at index %lu in test #%lu (expected %d, actual %d):\n%ls\n", i, which + 1, expected_indents.at(i), indents.at(i), text.c_str()); + break; //don't keep showing errors for the rest of the line + } + } + + } +} + static void test_utils() { say(L"Testing utils"); @@ -846,6 +1048,11 @@ static void test_abbreviations(void) expanded = reader_expand_abbreviation_in_command(L"of gc", wcslen(L"of gc"), &result); if (expanded) err(L"gc incorrectly expanded on line %ld", (long)__LINE__); + /* others should not be */ + expanded = reader_expand_abbreviation_in_command(L"command gc", wcslen(L"command gc"), &result); + if (expanded) err(L"gc incorrectly expanded on line %ld", (long)__LINE__); + + env_pop(); } @@ -1173,8 +1380,44 @@ static void test_complete(void) assert(completions.size() == 2); assert(completions.at(0).completion == L"$Foo1"); assert(completions.at(1).completion == L"$Bar1"); + + completions.clear(); + complete(L"echo (/bin/mkdi", completions, COMPLETION_REQUEST_DEFAULT); + assert(completions.size() == 1); + assert(completions.at(0).completion == L"r"); + + completions.clear(); + complete(L"echo (ls /bin/mkdi", completions, COMPLETION_REQUEST_DEFAULT); + assert(completions.size() == 1); + assert(completions.at(0).completion == L"r"); + + completions.clear(); + complete(L"echo (command ls /bin/mkdi", completions, COMPLETION_REQUEST_DEFAULT); + assert(completions.size() == 1); + assert(completions.at(0).completion == L"r"); + + /* Add a function and test completing it in various ways */ + struct function_data_t func_data; + func_data.name = L"scuttlebutt"; + func_data.definition = L"echo gongoozle"; + function_add(func_data, parser_t::principal_parser()); + + /* Complete a function name */ + completions.clear(); + complete(L"echo (scuttlebut", completions, COMPLETION_REQUEST_DEFAULT); + assert(completions.size() == 1); + assert(completions.at(0).completion == L"t"); + /* But not with the command prefix */ + completions.clear(); + complete(L"echo (command scuttlebut", completions, COMPLETION_REQUEST_DEFAULT); + assert(completions.size() == 0); + /* Not with the builtin prefix */ + completions.clear(); + complete(L"echo (builtin scuttlebut", completions, COMPLETION_REQUEST_DEFAULT); + assert(completions.size() == 0); + complete_set_variable_names(NULL); } @@ -1892,6 +2135,481 @@ void history_tests_t::test_history_speed(void) delete hist; } +static void test_new_parser_correctness(void) +{ + say(L"Testing new parser!"); + const struct parser_test_t + { + const wchar_t *src; + bool ok; + } + parser_tests[] = + { + {L"; ; ; ", true}, + {L"if ; end", false}, + {L"if true ; end", true}, + {L"if true; end ; end", false}, + {L"if end; end ; end", false}, + {L"if end", false}, + {L"end", false}, + {L"for i i", false}, + {L"for i in a b c ; end", true} + }; + + for (size_t i=0; i < sizeof parser_tests / sizeof *parser_tests; i++) + { + const parser_test_t *test = &parser_tests[i]; + + parse_node_tree_t parse_tree; + bool success = parse_t::parse(test->src, parse_flag_none, &parse_tree, NULL); + say(L"%lu / %lu: Parse \"%ls\": %s", i+1, sizeof parser_tests / sizeof *parser_tests, test->src, success ? "yes" : "no"); + if (success && ! test->ok) + { + err(L"\"%ls\" should NOT have parsed, but did", test->src); + } + else if (! success && test->ok) + { + err(L"\"%ls\" should have parsed, but failed", test->src); + } + } + say(L"Parse tests complete"); +} + +struct parser_fuzz_token_t +{ + parse_token_type_t token_type; + parse_keyword_t keyword; + + parser_fuzz_token_t() : token_type(FIRST_TERMINAL_TYPE), keyword(parse_keyword_none) + { + } +}; + +static bool increment(std::vector<parser_fuzz_token_t> &tokens) +{ + size_t i, end = tokens.size(); + for (i=0; i < end; i++) + { + bool wrapped = false; + + struct parser_fuzz_token_t &token = tokens[i]; + bool incremented_in_keyword = false; + if (token.token_type == parse_token_type_string) + { + // try incrementing the keyword + token.keyword++; + if (token.keyword <= LAST_KEYWORD) + { + incremented_in_keyword = true; + } + else + { + token.keyword = parse_keyword_none; + incremented_in_keyword = false; + } + } + + if (! incremented_in_keyword) + { + token.token_type++; + if (token.token_type > LAST_TERMINAL_TYPE) + { + token.token_type = FIRST_TERMINAL_TYPE; + wrapped = true; + } + } + + if (! wrapped) + { + break; + } + } + return i == end; +} + +static void test_new_parser_fuzzing(void) +{ + say(L"Fuzzing parser (node size: %lu)", sizeof(parse_node_t)); + double start = timef(); + bool log_it = false; + // ensure nothing crashes + size_t max = 4; + for (size_t len=1; len <= max; len++) + { + if (log_it) + fprintf(stderr, "%lu / %lu...", len, max); + std::vector<parser_fuzz_token_t> tokens(len); + size_t count = 0; + parse_t parser; + parse_node_tree_t parse_tree; + do + { + parser.clear(); + parse_tree.clear(); + count++; + for (size_t i=0; i < len; i++) + { + const parser_fuzz_token_t &token = tokens[i]; + parser.parse_1_token(token.token_type, token.keyword, &parse_tree, NULL); + } + + // keep going until we wrap + } + while (! increment(tokens)); + if (log_it) + fprintf(stderr, "done (%lu)\n", count); + } + double end = timef(); + if (log_it) + say(L"All fuzzed in %f seconds!", end - start); +} + +// Parse a statement, returning the command, args (joined by spaces), and the decoration. Returns true if successful. +static bool test_1_parse_ll2(const wcstring &src, wcstring *out_cmd, wcstring *out_joined_args, enum parse_statement_decoration_t *out_deco) +{ + out_cmd->clear(); + out_joined_args->clear(); + *out_deco = parse_statement_decoration_none; + + bool result = false; + parse_node_tree_t tree; + if (parse_t::parse(src, parse_flag_none, &tree, NULL)) + { + /* Get the statement. Should only have one */ + const parse_node_tree_t::parse_node_list_t stmt_nodes = tree.find_nodes(tree.at(0), symbol_plain_statement); + if (stmt_nodes.size() != 1) + { + say(L"Unexpected number of statements (%lu) found in '%ls'", stmt_nodes.size(), src.c_str()); + return false; + } + const parse_node_t &stmt = *stmt_nodes.at(0); + + /* Return its decoration */ + *out_deco = tree.decoration_for_plain_statement(stmt); + + /* Return its command */ + tree.command_for_plain_statement(stmt, src, out_cmd); + + /* Return arguments separated by spaces */ + const parse_node_tree_t::parse_node_list_t arg_nodes = tree.find_nodes(stmt, symbol_argument); + for (size_t i=0; i < arg_nodes.size(); i++) + { + if (i > 0) out_joined_args->push_back(L' '); + out_joined_args->append(arg_nodes.at(i)->get_source(src)); + } + result = true; + } + return result; +} + +/* Test the LL2 (two token lookahead) nature of the parser by exercising the special builtin and command handling. In particular, 'command foo' should be a decorated statement 'foo' but 'command --help' should be an undecorated statement 'command' with argument '--help', and NOT attempt to run a command called '--help' */ +static void test_new_parser_ll2(void) +{ + say(L"Testing parser two-token lookahead"); + + const struct + { + wcstring src; + wcstring cmd; + wcstring args; + enum parse_statement_decoration_t deco; + } tests[] = + { + {L"echo hello", L"echo", L"hello", parse_statement_decoration_none}, + {L"command echo hello", L"echo", L"hello", parse_statement_decoration_command}, + {L"command command hello", L"command", L"hello", parse_statement_decoration_command}, + {L"builtin command hello", L"command", L"hello", parse_statement_decoration_builtin}, + {L"command --help", L"command", L"--help", parse_statement_decoration_none}, + {L"command -h", L"command", L"-h", parse_statement_decoration_none}, + {L"command", L"command", L"", parse_statement_decoration_none}, + {L"command -", L"command", L"-", parse_statement_decoration_none}, + {L"command --", L"command", L"--", parse_statement_decoration_none}, + {L"builtin --names", L"builtin", L"--names", parse_statement_decoration_none}, + {L"function", L"function", L"", parse_statement_decoration_none}, + {L"function --help", L"function", L"--help", parse_statement_decoration_none} + }; + + for (size_t i=0; i < sizeof tests / sizeof *tests; i++) + { + wcstring cmd, args; + enum parse_statement_decoration_t deco = parse_statement_decoration_none; + bool success = test_1_parse_ll2(tests[i].src, &cmd, &args, &deco); + if (! success) + err(L"Parse of '%ls' failed on line %ld", tests[i].cmd.c_str(), (long)__LINE__); + if (cmd != tests[i].cmd) + err(L"When parsing '%ls', expected command '%ls' but got '%ls' on line %ld", tests[i].src.c_str(), tests[i].cmd.c_str(), cmd.c_str(), (long)__LINE__); + if (args != tests[i].args) + err(L"When parsing '%ls', expected args '%ls' but got '%ls' on line %ld", tests[i].src.c_str(), tests[i].args.c_str(), args.c_str(), (long)__LINE__); + if (deco != tests[i].deco) + err(L"When parsing '%ls', expected decoration %d but got %d on line %ld", tests[i].src.c_str(), (int)tests[i].deco, (int)deco, (long)__LINE__); + } +} + +static void test_new_parser_ad_hoc() +{ + /* Very ad-hoc tests for issues encountered */ + say(L"Testing new parser ad hoc tests"); + + /* Ensure that 'case' terminates a job list */ + const wcstring src = L"switch foo ; case bar; case baz; end"; + parse_node_tree_t parse_tree; + bool success = parse_t::parse(src, parse_flag_none, &parse_tree, NULL); + if (! success) + { + err(L"Parsing failed"); + } + + /* Expect three case_item_lists: one for each case, and a terminal one. The bug was that we'd try to run a command 'case' */ + const parse_node_t &root = parse_tree.at(0); + const parse_node_tree_t::parse_node_list_t node_list = parse_tree.find_nodes(root, symbol_case_item_list); + if (node_list.size() != 3) + { + err(L"Expected 3 case item nodes, found %lu", node_list.size()); + } +} + +static void test_new_parser_errors(void) +{ + say(L"Testing new parser error reporting"); + const struct + { + const wchar_t *src; + parse_error_code_t code; + } + tests[] = + { + {L"echo (abc", parse_error_tokenizer}, + + {L"end", parse_error_unbalancing_end}, + {L"echo hi ; end", parse_error_unbalancing_end}, + + {L"else", parse_error_unbalancing_else}, + {L"if true ; end ; else", parse_error_unbalancing_else}, + + {L"case", parse_error_unbalancing_case}, + {L"if true ; case ; end", parse_error_unbalancing_case} + }; + + for (size_t i = 0; i < sizeof tests / sizeof *tests; i++) + { + const wcstring src = tests[i].src; + parse_error_code_t expected_code = tests[i].code; + + parse_error_list_t errors; + parse_node_tree_t parse_tree; + bool success = parse_t::parse(src, parse_flag_none, &parse_tree, &errors); + if (success) + { + err(L"Source '%ls' was expected to fail to parse, but succeeded", src.c_str()); + } + + if (errors.size() != 1) + { + err(L"Source '%ls' was expected to produce 1 error, but instead produced %lu errors", src.c_str(), errors.size()); + } + else if (errors.at(0).code != expected_code) + { + err(L"Source '%ls' was expected to produce error code %lu, but instead produced error code %lu", src.c_str(), expected_code, (unsigned long)errors.at(0).code); + for (size_t i=0; i < errors.size(); i++) + { + err(L"\t\t%ls", errors.at(i).describe(src).c_str()); + } + } + + } + +} + +static void test_highlighting(void) +{ + say(L"Testing syntax highlighting"); + if (system("mkdir -p /tmp/fish_highlight_test/")) err(L"mkdir failed"); + if (system("touch /tmp/fish_highlight_test/foo")) err(L"touch failed"); + if (system("touch /tmp/fish_highlight_test/bar")) err(L"touch failed"); + + // Here are the components of our source and the colors we expect those to be + struct highlight_component_t { + const wchar_t *txt; + int color; + }; + + const highlight_component_t components1[] = + { + {L"echo", HIGHLIGHT_COMMAND}, + {L"/tmp/fish_highlight_test/foo", HIGHLIGHT_PARAM | HIGHLIGHT_VALID_PATH}, + {L"&", HIGHLIGHT_END}, + {NULL, -1} + }; + + const highlight_component_t components2[] = + { + {L"command", HIGHLIGHT_COMMAND}, + {L"echo", HIGHLIGHT_COMMAND}, + {L"abc", HIGHLIGHT_PARAM}, + {L"/tmp/fish_highlight_test/foo", HIGHLIGHT_PARAM | HIGHLIGHT_VALID_PATH}, + {L"&", HIGHLIGHT_END}, + {NULL, -1} + }; + + const highlight_component_t components3[] = + { + {L"if command ls", HIGHLIGHT_COMMAND}, + {L"; ", HIGHLIGHT_END}, + {L"echo", HIGHLIGHT_COMMAND}, + {L"abc", HIGHLIGHT_PARAM}, + {L"; ", HIGHLIGHT_END}, + {L"/bin/definitely_not_a_command", HIGHLIGHT_ERROR}, + {L"; ", HIGHLIGHT_END}, + {L"end", HIGHLIGHT_COMMAND}, + {NULL, -1} + }; + + /* Verify that cd shows errors for non-directories */ + const highlight_component_t components4[] = + { + {L"cd", HIGHLIGHT_COMMAND}, + {L"/tmp/fish_highlight_test", HIGHLIGHT_PARAM | HIGHLIGHT_VALID_PATH}, + {NULL, -1} + }; + + const highlight_component_t components5[] = + { + {L"cd", HIGHLIGHT_COMMAND}, + {L"/tmp/fish_highlight_test/foo", HIGHLIGHT_ERROR}, + {NULL, -1} + }; + + const highlight_component_t components6[] = + { + {L"cd", HIGHLIGHT_COMMAND}, + {L"--help", HIGHLIGHT_PARAM}, + {L"-h", HIGHLIGHT_PARAM}, + {L"definitely_not_a_directory", HIGHLIGHT_ERROR}, + {NULL, -1} + }; + + // Command substitutions + const highlight_component_t components7[] = + { + {L"echo", HIGHLIGHT_COMMAND}, + {L"param1", HIGHLIGHT_PARAM}, + {L"(", HIGHLIGHT_OPERATOR}, + {L"ls", HIGHLIGHT_COMMAND}, + {L"param2", HIGHLIGHT_PARAM}, + {L")", HIGHLIGHT_OPERATOR}, + {NULL, -1} + }; + + // Redirections substitutions + const highlight_component_t components8[] = + { + {L"echo", HIGHLIGHT_COMMAND}, + {L"param1", HIGHLIGHT_PARAM}, + + /* Input redirection */ + {L"<", HIGHLIGHT_REDIRECTION}, + {L"/bin/echo", HIGHLIGHT_REDIRECTION}, + + /* Output redirection to a valid fd */ + {L"1>&2", HIGHLIGHT_REDIRECTION}, + + /* Output redirection to an invalid fd */ + {L"2>&", HIGHLIGHT_REDIRECTION}, + {L"LOL", HIGHLIGHT_ERROR}, + + /* Just a param, not a redirection */ + {L"/tmp/blah", HIGHLIGHT_PARAM}, + + /* Input redirection from directory */ + {L"<", HIGHLIGHT_REDIRECTION}, + {L"/tmp/", HIGHLIGHT_ERROR}, + + /* Output redirection to an invalid path */ + {L"3>", HIGHLIGHT_REDIRECTION}, + {L"/not/a/valid/path/nope", HIGHLIGHT_ERROR}, + + /* Output redirection to directory */ + {L"3>", HIGHLIGHT_REDIRECTION}, + {L"/tmp/nope/", HIGHLIGHT_ERROR}, + + + /* Redirections to overflow fd */ + {L"99999999999999999999>&2", HIGHLIGHT_ERROR}, + {L"2>&", HIGHLIGHT_REDIRECTION}, + {L"99999999999999999999", HIGHLIGHT_ERROR}, + + /* Output redirection containing a command substitution */ + {L"4>", HIGHLIGHT_REDIRECTION}, + {L"(", HIGHLIGHT_OPERATOR}, + {L"echo", HIGHLIGHT_COMMAND}, + {L"/tmp/somewhere", HIGHLIGHT_PARAM}, + {L")", HIGHLIGHT_OPERATOR}, + + /* Just another param */ + {L"param2", HIGHLIGHT_PARAM}, + {NULL, -1} + }; + + const highlight_component_t components9[] = + { + {L"end", HIGHLIGHT_ERROR}, + {L";", HIGHLIGHT_END}, + {L"if", HIGHLIGHT_COMMAND}, + {L"end", HIGHLIGHT_ERROR}, + {NULL, -1} + }; + + + const highlight_component_t *tests[] = {components1, components2, components3, components4, components5, components6, components7, components8, components9}; + for (size_t which = 0; which < sizeof tests / sizeof *tests; which++) + { + const highlight_component_t *components = tests[which]; + // Count how many we have + size_t component_count = 0; + while (components[component_count].txt != NULL) + { + component_count++; + } + + // Generate the text + wcstring text; + std::vector<int> expected_colors; + for (size_t i=0; i < component_count; i++) + { + if (i > 0) + { + text.push_back(L' '); + expected_colors.push_back(0); + } + text.append(components[i].txt); + expected_colors.resize(text.size(), components[i].color); + } + assert(expected_colors.size() == text.size()); + + std::vector<int> colors(text.size()); + highlight_shell(text, colors, 20, NULL, env_vars_snapshot_t()); + + if (expected_colors.size() != colors.size()) + { + err(L"Color vector has wrong size! Expected %lu, actual %lu", expected_colors.size(), colors.size()); + } + assert(expected_colors.size() == colors.size()); + for (size_t i=0; i < text.size(); i++) + { + // Hackish space handling. We don't care about the colors in spaces. + if (text.at(i) == L' ') + continue; + + if (expected_colors.at(i) != colors.at(i)) + { + const wcstring spaces(i, L' '); + err(L"Wrong color at index %lu in text (expected %#x, actual %#x):\n%ls\n%ls^", i, expected_colors.at(i), colors.at(i), text.c_str(), spaces.c_str()); + } + } + } + + system("rm -Rf /tmp/fish_highlight_test"); +} /** Main test @@ -1899,53 +2617,62 @@ void history_tests_t::test_history_speed(void) int main(int argc, char **argv) { setlocale(LC_ALL, ""); - srand(time(0)); + //srand(time(0)); configure_thread_assertions_for_testing(); program_name=L"(ignore)"; + s_arguments = argv + 1; say(L"Testing low-level functionality"); - say(L"Lines beginning with '(ignore):' are not errors, they are warning messages\ngenerated by the fish parser library when given broken input, and can be\nignored. All actual errors begin with 'Error:'."); set_main_thread(); setup_fork_guards(); - proc_init(); + //proc_init(); //disabling this prevents catching SIGINT event_init(); function_init(); builtin_init(); reader_init(); env_init(); - test_unescape_sane(); - test_escape_crazy(); - test_format(); - test_convert(); - test_convert_nulls(); - test_tok(); - test_fork(); - test_iothread(); - test_parser(); - test_utils(); - test_escape_sequences(); - test_lru(); - test_expand(); - test_fuzzy_match(); - test_abbreviations(); - test_test(); - test_path(); - test_word_motion(); - test_is_potential_path(); - test_colors(); - test_complete(); - test_completion_insertions(); - test_autosuggestion_combining(); - test_autosuggest_suggest_special(); - history_tests_t::test_history(); - history_tests_t::test_history_merge(); - history_tests_t::test_history_races(); - history_tests_t::test_history_formats(); + if (should_test_function("highlighting")) test_highlighting(); + if (should_test_function("new_parser_ll2")) test_new_parser_ll2(); + if (should_test_function("new_parser_fuzzing")) test_new_parser_fuzzing(); //fuzzing is expensive + if (should_test_function("new_parser_correctness")) test_new_parser_correctness(); + if (should_test_function("new_parser_ad_hoc")) test_new_parser_ad_hoc(); + if (should_test_function("new_parser_errors")) test_new_parser_errors(); + if (should_test_function("escape")) test_unescape_sane(); + if (should_test_function("escape")) test_escape_crazy(); + if (should_test_function("format")) test_format(); + if (should_test_function("convert")) test_convert(); + if (should_test_function("convert_nulls")) test_convert_nulls(); + if (should_test_function("tok")) test_tok(); + if (should_test_function("fork")) test_fork(); + if (should_test_function("iothread")) test_iothread(); + if (should_test_function("parser")) test_parser(); + if (should_test_function("indents")) test_indents(); + if (should_test_function("utils")) test_utils(); + if (should_test_function("escape_sequences")) test_escape_sequences(); + if (should_test_function("lru")) test_lru(); + if (should_test_function("expand")) test_expand(); + if (should_test_function("fuzzy_match")) test_fuzzy_match(); + if (should_test_function("abbreviations")) test_abbreviations(); + if (should_test_function("test")) test_test(); + if (should_test_function("path")) test_path(); + if (should_test_function("word_motion")) test_word_motion(); + if (should_test_function("is_potential_path")) test_is_potential_path(); + if (should_test_function("colors")) test_colors(); + if (should_test_function("complete")) test_complete(); + if (should_test_function("completion_insertions")) test_completion_insertions(); + if (should_test_function("autosuggestion_combining")) test_autosuggestion_combining(); + if (should_test_function("autosuggest_suggest_special")) test_autosuggest_suggest_special(); + if (should_test_function("history")) history_tests_t::test_history(); + if (should_test_function("history_merge")) history_tests_t::test_history_merge(); + if (should_test_function("history_races")) history_tests_t::test_history_races(); + if (should_test_function("history_formats")) history_tests_t::test_history_formats(); //history_tests_t::test_history_speed(); say(L"Encountered %d errors in low-level tests", err_count); + if (s_test_run_count == 0) + say(L"*** No Tests Were Actually Run! ***"); /* Skip performance tests for now, since they seem to hang when running from inside make (?) |