aboutsummaryrefslogtreecommitdiffhomepage
path: root/fish_tests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'fish_tests.cpp')
-rw-r--r--fish_tests.cpp821
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 (?)