1 // Various bug and feature tests. Compiled and run by make test.
2 #include "config.h" // IWYU pragma: keep
3
4 // IWYU pragma: no_include <cstring>
5 // IWYU pragma: no_include <cstddef>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <libgen.h>
9 #include <limits.h>
10 #include <math.h>
11 #include <pthread.h>
12 #include <signal.h>
13 #include <stdarg.h>
14 #include <stddef.h>
15 #include <stdint.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <sys/select.h>
19 #include <sys/stat.h>
20 #include <sys/time.h>
21 #include <sys/types.h>
22 #include <sys/utsname.h>
23 #include <sys/wait.h>
24 #include <time.h>
25 #include <unistd.h>
26 #include <wctype.h>
27
28 #include <algorithm>
29 #include <array>
30 #include <atomic>
31 #include <chrono>
32 #include <cmath>
33 #include <cstring>
34 #include <cwchar>
35 #include <functional>
36 #include <memory>
37 #include <set>
38 #include <string>
39 #include <thread>
40 #include <utility>
41 #include <vector>
42
43 #include "ast.h"
44 #include "autoload.h"
45 #include "builtin.h"
46 #include "color.h"
47 #include "common.h"
48 #include "complete.h"
49 #include "env.h"
50 #include "env_universal_common.h"
51 #include "event.h"
52 #include "expand.h"
53 #include "fallback.h" // IWYU pragma: keep
54 #include "fd_monitor.h"
55 #include "function.h"
56 #include "future_feature_flags.h"
57 #include "highlight.h"
58 #include "history.h"
59 #include "input.h"
60 #include "input_common.h"
61 #include "io.h"
62 #include "iothread.h"
63 #include "kill.h"
64 #include "lru.h"
65 #include "maybe.h"
66 #include "operation_context.h"
67 #include "pager.h"
68 #include "parse_constants.h"
69 #include "parse_tree.h"
70 #include "parse_util.h"
71 #include "parser.h"
72 #include "path.h"
73 #include "proc.h"
74 #include "reader.h"
75 #include "redirection.h"
76 #include "screen.h"
77 #include "signal.h"
78 #include "termsize.h"
79 #include "timer.h"
80 #include "tokenizer.h"
81 #include "topic_monitor.h"
82 #include "utf8.h"
83 #include "util.h"
84 #include "wcstringutil.h"
85 #include "wildcard.h"
86 #include "wutil.h" // IWYU pragma: keep
87
88 static const char *const *s_arguments;
89 static int s_test_run_count = 0;
90
91 #define system_assert(command) \
92 if (system(command)) { \
93 err(L"Non-zero result on line %d: %s", __LINE__, command); \
94 }
95
96 // Indicate if we should test the given function. Either we test everything (all arguments) or we
97 // run only tests that have a prefix in s_arguments.
98 // If \p default_on is set, then allow no args to run this test by default.
should_test_function(const char * func_name,bool default_on=true)99 static bool should_test_function(const char *func_name, bool default_on = true) {
100 bool result = false;
101 if (!s_arguments || !s_arguments[0]) {
102 // No args, test if defaulted on.
103 result = default_on;
104 } else {
105 for (size_t i = 0; s_arguments[i] != NULL; i++) {
106 if (!std::strncmp(func_name, s_arguments[i], std::strlen(s_arguments[i]))) {
107 // Prefix match.
108 result = true;
109 break;
110 }
111 }
112 }
113 if (result) s_test_run_count++;
114 return result;
115 }
116
117 /// The number of tests to run.
118 #define ESCAPE_TEST_COUNT 100000
119 /// The average length of strings to unescape.
120 #define ESCAPE_TEST_LENGTH 100
121 /// The highest character number of character to try and escape.
122 #define ESCAPE_TEST_CHAR 4000
123
124 /// Number of encountered errors.
125 static int err_count = 0;
126
127 /// Print formatted output.
say(const wchar_t * fmt,...)128 static void say(const wchar_t *fmt, ...) {
129 va_list va;
130 va_start(va, fmt);
131 std::vfwprintf(stdout, fmt, va);
132 va_end(va);
133 std::fwprintf(stdout, L"\n");
134 }
135
136 /// Print formatted error string.
err(const wchar_t * blah,...)137 static void err(const wchar_t *blah, ...) {
138 va_list va;
139 va_start(va, blah);
140 err_count++;
141
142 // Show errors in red.
143 std::fputws(L"\x1B[31m", stdout);
144 std::fwprintf(stdout, L"Error: ");
145 std::vfwprintf(stdout, blah, va);
146 va_end(va);
147
148 // Return to normal color.
149 std::fputws(L"\x1B[0m", stdout);
150 std::fwprintf(stdout, L"\n");
151 }
152
153 /// Joins a wcstring_list_t via commas.
comma_join(const wcstring_list_t & lst)154 static wcstring comma_join(const wcstring_list_t &lst) {
155 wcstring result;
156 for (size_t i = 0; i < lst.size(); i++) {
157 if (i > 0) {
158 result.push_back(L',');
159 }
160 result.append(lst.at(i));
161 }
162 return result;
163 }
164
165 static std::vector<std::string> pushed_dirs;
166
167 /// Helper to chdir and then update $PWD.
pushd(const char * path)168 static bool pushd(const char *path) {
169 char cwd[PATH_MAX] = {};
170 if (getcwd(cwd, sizeof cwd) == NULL) {
171 err(L"getcwd() from pushd() failed: errno = %d", errno);
172 return false;
173 }
174 pushed_dirs.push_back(cwd);
175
176 // We might need to create the directory. We don't care if this fails due to the directory
177 // already being present.
178 mkdir(path, 0770);
179
180 int ret = chdir(path);
181 if (ret != 0) {
182 err(L"chdir(\"%s\") from pushd() failed: errno = %d", path, errno);
183 return false;
184 }
185
186 env_stack_t::principal().set_pwd_from_getcwd();
187 return true;
188 }
189
popd()190 static void popd() {
191 const std::string &old_cwd = pushed_dirs.back();
192 if (chdir(old_cwd.c_str()) == -1) {
193 err(L"chdir(\"%s\") from popd() failed: errno = %d", old_cwd.c_str(), errno);
194 }
195 pushed_dirs.pop_back();
196 env_stack_t::principal().set_pwd_from_getcwd();
197 }
198
199 // Helper to return a string whose length greatly exceeds PATH_MAX.
get_overlong_path()200 wcstring get_overlong_path() {
201 wcstring longpath;
202 longpath.reserve(PATH_MAX * 2 + 10);
203 while (longpath.size() <= PATH_MAX * 2) {
204 longpath += L"/overlong";
205 }
206 return longpath;
207 }
208
209 // The odd formulation of these macros is to avoid "multiple unary operator" warnings from oclint
210 // were we to use the more natural "if (!(e)) err(..." form. We have to do this because the rules
211 // for the C preprocessor make it practically impossible to embed a comment in the body of a macro.
212 #define do_test(e) \
213 do { \
214 if (e) { \
215 ; \
216 } else { \
217 err(L"Test failed on line %lu: %s", __LINE__, #e); \
218 } \
219 } while (0)
220
221 #define do_test_from(e, from) \
222 do { \
223 if (e) { \
224 ; \
225 } else { \
226 err(L"Test failed on line %lu (from %lu): %s", __LINE__, from, #e); \
227 } \
228 } while (0)
229
230 #define do_test1(e, msg) \
231 do { \
232 if (e) { \
233 ; \
234 } else { \
235 err(L"Test failed on line %lu: %ls", __LINE__, (msg)); \
236 } \
237 } while (0)
238
239 /// Test that the fish functions for converting strings to numbers work.
test_str_to_num()240 static void test_str_to_num() {
241 say(L"Testing str_to_num");
242 const wchar_t *end;
243 int i;
244 long l;
245
246 i = fish_wcstoi(L"");
247 do_test1(errno == EINVAL && i == 0, L"converting empty string to int did not fail");
248 i = fish_wcstoi(L" \n ");
249 do_test1(errno == EINVAL && i == 0, L"converting whitespace string to int did not fail");
250 i = fish_wcstoi(L"123");
251 do_test1(errno == 0 && i == 123, L"converting valid num to int did not succeed");
252 i = fish_wcstoi(L"-123");
253 do_test1(errno == 0 && i == -123, L"converting valid num to int did not succeed");
254 i = fish_wcstoi(L" 345 ");
255 do_test1(errno == 0 && i == 345, L"converting valid num to int did not succeed");
256 i = fish_wcstoi(L" -345 ");
257 do_test1(errno == 0 && i == -345, L"converting valid num to int did not succeed");
258 i = fish_wcstoi(L"x345");
259 do_test1(errno == EINVAL && i == 0, L"converting invalid num to int did not fail");
260 i = fish_wcstoi(L" x345");
261 do_test1(errno == EINVAL && i == 0, L"converting invalid num to int did not fail");
262 i = fish_wcstoi(L"456 x");
263 do_test1(errno == -1 && i == 456, L"converting invalid num to int did not fail");
264 i = fish_wcstoi(L"99999999999999999999999");
265 do_test1(errno == ERANGE && i == INT_MAX, L"converting invalid num to int did not fail");
266 i = fish_wcstoi(L"-99999999999999999999999");
267 do_test1(errno == ERANGE && i == INT_MIN, L"converting invalid num to int did not fail");
268 i = fish_wcstoi(L"567]", &end);
269 do_test1(errno == -1 && i == 567 && *end == L']',
270 L"converting valid num to int did not succeed");
271 // This is subtle. "567" in base 8 is "375" in base 10. The final "8" is not converted.
272 i = fish_wcstoi(L"5678", &end, 8);
273 do_test1(errno == -1 && i == 375 && *end == L'8',
274 L"converting invalid num to int did not fail");
275
276 l = fish_wcstol(L"");
277 do_test1(errno == EINVAL && l == 0, L"converting empty string to long did not fail");
278 l = fish_wcstol(L" \t ");
279 do_test1(errno == EINVAL && l == 0, L"converting whitespace string to long did not fail");
280 l = fish_wcstol(L"123");
281 do_test1(errno == 0 && l == 123, L"converting valid num to long did not succeed");
282 l = fish_wcstol(L"-123");
283 do_test1(errno == 0 && l == -123, L"converting valid num to long did not succeed");
284 l = fish_wcstol(L" 345 ");
285 do_test1(errno == 0 && l == 345, L"converting valid num to long did not succeed");
286 l = fish_wcstol(L" -345 ");
287 do_test1(errno == 0 && l == -345, L"converting valid num to long did not succeed");
288 l = fish_wcstol(L"x345");
289 do_test1(errno == EINVAL && l == 0, L"converting invalid num to long did not fail");
290 l = fish_wcstol(L" x345");
291 do_test1(errno == EINVAL && l == 0, L"converting invalid num to long did not fail");
292 l = fish_wcstol(L"456 x");
293 do_test1(errno == -1 && l == 456, L"converting invalid num to long did not fail");
294 l = fish_wcstol(L"99999999999999999999999");
295 do_test1(errno == ERANGE && l == LONG_MAX, L"converting invalid num to long did not fail");
296 l = fish_wcstol(L"-99999999999999999999999");
297 do_test1(errno == ERANGE && l == LONG_MIN, L"converting invalid num to long did not fail");
298 l = fish_wcstol(L"567]", &end);
299 do_test1(errno == -1 && l == 567 && *end == L']',
300 L"converting valid num to long did not succeed");
301 // This is subtle. "567" in base 8 is "375" in base 10. The final "8" is not converted.
302 l = fish_wcstol(L"5678", &end, 8);
303 do_test1(errno == -1 && l == 375 && *end == L'8',
304 L"converting invalid num to long did not fail");
305 }
306
307 enum class test_enum { alpha, beta, gamma, COUNT };
308
309 template <>
310 struct enum_info_t<test_enum> {
311 static constexpr auto count = test_enum::COUNT;
312 };
313
test_enum_set()314 static void test_enum_set() {
315 say(L"Testing enum set");
316 enum_set_t<test_enum> es;
317 do_test(es.none());
318 do_test(!es.any());
319 do_test(es.to_raw() == 0);
320 do_test(es == enum_set_t<test_enum>::from_raw(0));
321 do_test(es != enum_set_t<test_enum>::from_raw(1));
322
323 es.set(test_enum::beta);
324 do_test(es.get(test_enum::beta));
325 do_test(!es.get(test_enum::alpha));
326 do_test(es & test_enum::beta);
327 do_test(!(es & test_enum::alpha));
328 do_test(es.to_raw() == 2);
329 do_test(es == enum_set_t<test_enum>::from_raw(2));
330 do_test(es == enum_set_t<test_enum>{test_enum::beta});
331 do_test(es != enum_set_t<test_enum>::from_raw(3));
332 do_test(es.any());
333 do_test(!es.none());
334
335 do_test((enum_set_t<test_enum>{test_enum::beta} | test_enum::alpha).to_raw() == 3);
336 do_test((enum_set_t<test_enum>{test_enum::beta} | enum_set_t<test_enum>{test_enum::alpha})
337 .to_raw() == 3);
338
339 unsigned idx = 0;
340 for (auto v : enum_iter_t<test_enum>{}) {
341 do_test(static_cast<unsigned>(v) == idx);
342 idx++;
343 }
344 do_test(static_cast<unsigned>(test_enum::COUNT) == idx);
345 }
346
test_enum_array()347 static void test_enum_array() {
348 say(L"Testing enum array");
349 enum_array_t<std::string, test_enum> es{};
350 do_test(es.size() == enum_count<test_enum>());
351 es[test_enum::beta] = "abc";
352 do_test(es[test_enum::beta] == "abc");
353 es.at(test_enum::gamma) = "def";
354 do_test(es.at(test_enum::gamma) == "def");
355 }
356
357 /// Test sane escapes.
test_unescape_sane()358 static void test_unescape_sane() {
359 const struct test_t {
360 const wchar_t *input;
361 const wchar_t *expected;
362 } tests[] = {
363 {L"abcd", L"abcd"}, {L"'abcd'", L"abcd"},
364 {L"'abcd\\n'", L"abcd\\n"}, {L"\"abcd\\n\"", L"abcd\\n"},
365 {L"\"abcd\\n\"", L"abcd\\n"}, {L"\\143", L"c"},
366 {L"'\\143'", L"\\143"}, {L"\\n", L"\n"} // \n normally becomes newline
367 };
368 wcstring output;
369 for (const auto &test : tests) {
370 bool ret = unescape_string(test.input, &output, UNESCAPE_DEFAULT);
371 if (!ret) {
372 err(L"Failed to unescape '%ls'\n", test.input);
373 } else if (output != test.expected) {
374 err(L"In unescaping '%ls', expected '%ls' but got '%ls'\n", test.input, test.expected,
375 output.c_str());
376 }
377 }
378
379 // Test for overflow.
380 if (unescape_string(L"echo \\UFFFFFF", &output, UNESCAPE_DEFAULT)) {
381 err(L"Should not have been able to unescape \\UFFFFFF\n");
382 }
383 if (unescape_string(L"echo \\U110000", &output, UNESCAPE_DEFAULT)) {
384 err(L"Should not have been able to unescape \\U110000\n");
385 }
386 #if WCHAR_MAX != 0xffff
387 // TODO: Make this work on MS Windows.
388 if (!unescape_string(L"echo \\U10FFFF", &output, UNESCAPE_DEFAULT)) {
389 err(L"Should have been able to unescape \\U10FFFF\n");
390 }
391 #endif
392 }
393
394 /// Test the escaping/unescaping code by escaping/unescaping random strings and verifying that the
395 /// original string comes back.
test_escape_crazy()396 static void test_escape_crazy() {
397 say(L"Testing escaping and unescaping");
398 wcstring random_string;
399 wcstring escaped_string;
400 wcstring unescaped_string;
401 bool unescaped_success;
402 for (size_t i = 0; i < ESCAPE_TEST_COUNT; i++) {
403 random_string.clear();
404 while (random() % ESCAPE_TEST_LENGTH) {
405 random_string.push_back((random() % ESCAPE_TEST_CHAR) + 1);
406 }
407
408 escaped_string = escape_string(random_string, ESCAPE_ALL);
409 unescaped_success = unescape_string(escaped_string, &unescaped_string, UNESCAPE_DEFAULT);
410
411 if (!unescaped_success) {
412 err(L"Failed to unescape string <%ls>", escaped_string.c_str());
413 break;
414 } else if (unescaped_string != random_string) {
415 err(L"Escaped and then unescaped string '%ls', but got back a different string '%ls'",
416 random_string.c_str(), unescaped_string.c_str());
417 break;
418 }
419 }
420
421 // Verify that not using `ESCAPE_ALL` also escapes backslashes so we don't regress on issue
422 // #3892.
423 random_string = L"line 1\\n\nline 2";
424 escaped_string = escape_string(random_string, ESCAPE_NO_QUOTED);
425 unescaped_success = unescape_string(escaped_string, &unescaped_string, UNESCAPE_DEFAULT);
426 if (!unescaped_success) {
427 err(L"Failed to unescape string <%ls>", escaped_string.c_str());
428 } else if (unescaped_string != random_string) {
429 err(L"Escaped and then unescaped string '%ls', but got back a different string '%ls'",
430 random_string.c_str(), unescaped_string.c_str());
431 }
432 }
433
test_escape_quotes()434 static void test_escape_quotes() {
435 say(L"Testing escaping with quotes");
436 // These are "raw string literals"
437 do_test(parse_util_escape_string_with_quote(L"abc", L'\0') == L"abc");
438 do_test(parse_util_escape_string_with_quote(L"abc~def", L'\0') == L"abc\\~def");
439 do_test(parse_util_escape_string_with_quote(L"abc~def", L'\0', true) == L"abc~def");
440 do_test(parse_util_escape_string_with_quote(L"abc\\~def", L'\0') == L"abc\\\\\\~def");
441 do_test(parse_util_escape_string_with_quote(L"abc\\~def", L'\0', true) == L"abc\\\\~def");
442 do_test(parse_util_escape_string_with_quote(L"~abc", L'\0') == L"\\~abc");
443 do_test(parse_util_escape_string_with_quote(L"~abc", L'\0', true) == L"~abc");
444 do_test(parse_util_escape_string_with_quote(L"~abc|def", L'\0') == L"\\~abc\\|def");
445 do_test(parse_util_escape_string_with_quote(L"|abc~def", L'\0') == L"\\|abc\\~def");
446 do_test(parse_util_escape_string_with_quote(L"|abc~def", L'\0', true) == L"\\|abc~def");
447 do_test(parse_util_escape_string_with_quote(L"foo\nbar", L'\0') == L"foo\\nbar");
448
449 // Note tildes are not expanded inside quotes, so no_tilde is ignored with a quote.
450 do_test(parse_util_escape_string_with_quote(L"abc", L'\'') == L"abc");
451 do_test(parse_util_escape_string_with_quote(L"abc\\def", L'\'') == L"abc\\\\def");
452 do_test(parse_util_escape_string_with_quote(L"abc'def", L'\'') == L"abc\\'def");
453 do_test(parse_util_escape_string_with_quote(L"~abc'def", L'\'') == L"~abc\\'def");
454 do_test(parse_util_escape_string_with_quote(L"~abc'def", L'\'', true) == L"~abc\\'def");
455 do_test(parse_util_escape_string_with_quote(L"foo\nba'r", L'\'') == L"foo'\\n'ba\\'r");
456 do_test(parse_util_escape_string_with_quote(L"foo\\\\bar", L'\'') == L"foo\\\\\\\\bar");
457
458 do_test(parse_util_escape_string_with_quote(L"abc", L'"') == L"abc");
459 do_test(parse_util_escape_string_with_quote(L"abc\\def", L'"') == L"abc\\\\def");
460 do_test(parse_util_escape_string_with_quote(L"~abc'def", L'"') == L"~abc'def");
461 do_test(parse_util_escape_string_with_quote(L"~abc'def", L'"', true) == L"~abc'def");
462 do_test(parse_util_escape_string_with_quote(L"foo\nba'r", L'"') == L"foo\"\\n\"ba'r");
463 do_test(parse_util_escape_string_with_quote(L"foo\\\\bar", L'"') == L"foo\\\\\\\\bar");
464 }
465
test_format()466 static void test_format() {
467 say(L"Testing formatting functions");
468 struct {
469 unsigned long long val;
470 const char *expected;
471 } tests[] = {{0, "empty"}, {1, "1B"}, {2, "2B"},
472 {1024, "1kB"}, {1870, "1.8kB"}, {4322911, "4.1MB"}};
473 for (const auto &test : tests) {
474 char buff[128];
475 format_size_safe(buff, test.val);
476 do_test(!std::strcmp(buff, test.expected));
477 }
478
479 for (int j = -129; j <= 129; j++) {
480 char buff1[128], buff2[128];
481 format_long_safe(buff1, j);
482 sprintf(buff2, "%d", j);
483 do_test(!std::strcmp(buff1, buff2));
484
485 wchar_t wbuf1[128], wbuf2[128];
486 format_long_safe(wbuf1, j);
487 std::swprintf(wbuf2, 128, L"%d", j);
488 do_test(!std::wcscmp(wbuf1, wbuf2));
489 }
490
491 long q = LONG_MIN;
492 char buff1[128], buff2[128];
493 format_long_safe(buff1, q);
494 sprintf(buff2, "%ld", q);
495 do_test(!std::strcmp(buff1, buff2));
496 }
497
498 /// Helper to convert a narrow string to a sequence of hex digits.
str2hex(const char * input)499 static char *str2hex(const char *input) {
500 char *output = (char *)malloc(5 * std::strlen(input) + 1);
501 char *p = output;
502 for (; *input; input++) {
503 sprintf(p, "0x%02X ", (int)*input & 0xFF);
504 p += 5;
505 }
506 *p = '\0';
507 return output;
508 }
509
510 /// Test wide/narrow conversion by creating random strings and verifying that the original string
511 /// comes back through double conversion.
test_convert()512 static void test_convert() {
513 say(L"Testing wide/narrow string conversion");
514 for (int i = 0; i < ESCAPE_TEST_COUNT; i++) {
515 std::string orig{};
516 while (random() % ESCAPE_TEST_LENGTH) {
517 char c = random();
518 orig.push_back(c);
519 }
520
521 const wcstring w = str2wcstring(orig);
522 std::string n = wcs2string(w);
523 if (orig != n) {
524 char *o2 = str2hex(orig.c_str());
525 char *n2 = str2hex(n.c_str());
526 err(L"Line %d - %d: Conversion cycle of string:\n%4d chars: %s\n"
527 L"produced different string:\n%4d chars: %s",
528 __LINE__, i, orig.size(), o2, n.size(), n2);
529 free(o2);
530 free(n2);
531 }
532 }
533 }
534
535 /// Verify that ASCII narrow->wide conversions are correct.
test_convert_ascii()536 static void test_convert_ascii() {
537 std::string s(4096, '\0');
538 for (size_t i = 0; i < s.size(); i++) {
539 s[i] = (i % 10) + '0';
540 }
541
542 // Test a variety of alignments.
543 for (size_t left = 0; left < 16; left++) {
544 for (size_t right = 0; right < 16; right++) {
545 const char *start = s.data() + left;
546 size_t len = s.size() - left - right;
547 wcstring wide = str2wcstring(start, len);
548 std::string narrow = wcs2string(wide);
549 do_test(narrow == std::string(start, len));
550 }
551 }
552
553 // Put some non-ASCII bytes in and ensure it all still works.
554 for (char &c : s) {
555 char saved = c;
556 c = 0xF7;
557 do_test(wcs2string(str2wcstring(s)) == s);
558 c = saved;
559 }
560 }
561
562 /// fish uses the private-use range to encode bytes that could not be decoded using the user's
563 /// locale. If the input could be decoded, but decoded to private-use codepoints, then fish should
564 /// also use the direct encoding for those bytes. Verify that characters in the private use area are
565 /// correctly round-tripped. See #7723.
test_convert_private_use()566 static void test_convert_private_use() {
567 for (wchar_t wc = ENCODE_DIRECT_BASE; wc < ENCODE_DIRECT_END; wc++) {
568 // Encode the char via the locale. Do not use fish functions which interpret these
569 // specially.
570 char converted[MB_LEN_MAX];
571 mbstate_t state{};
572 size_t len = std::wcrtomb(converted, wc, &state);
573 if (len == static_cast<size_t>(-1)) {
574 // Could not be encoded in this locale.
575 continue;
576 }
577 std::string s(converted, len);
578
579 // Ask fish to decode this via str2wcstring.
580 // str2wcstring should notice that the decoded form collides with its private use and encode
581 // it directly.
582 wcstring ws = str2wcstring(s);
583
584 // Each byte should be encoded directly, and round tripping should work.
585 do_test(ws.size() == s.size());
586 do_test(wcs2string(ws) == s);
587 }
588 }
589
perf_convert_ascii()590 static void perf_convert_ascii() {
591 std::string s(128 * 1024, '\0');
592 for (size_t i = 0; i < s.size(); i++) {
593 s[i] = (i % 10) + '0';
594 }
595 (void)str2wcstring(s);
596
597 double start = timef();
598 const int iters = 1024;
599 for (int i = 0; i < iters; i++) {
600 (void)str2wcstring(s);
601 }
602 double end = timef();
603 auto usec = static_cast<unsigned long long>(((end - start) * 1E6) / iters);
604 say(L"ASCII string conversion perf: %lu bytes in %llu usec", s.size(), usec);
605 }
606
607 /// Verify correct behavior with embedded nulls.
test_convert_nulls()608 static void test_convert_nulls() {
609 say(L"Testing convert_nulls");
610 const wchar_t in[] = L"AAA\0BBB";
611 const size_t in_len = (sizeof in / sizeof *in) - 1;
612 const wcstring in_str = wcstring(in, in_len);
613 std::string out_str = wcs2string(in_str);
614 if (out_str.size() != in_len) {
615 err(L"Embedded nulls mishandled in wcs2string");
616 }
617 for (size_t i = 0; i < in_len; i++) {
618 if (in[i] != out_str.at(i)) {
619 err(L"Embedded nulls mishandled in wcs2string at index %lu", (unsigned long)i);
620 }
621 }
622
623 wcstring out_wstr = str2wcstring(out_str);
624 if (out_wstr.size() != in_len) {
625 err(L"Embedded nulls mishandled in str2wcstring");
626 }
627 for (size_t i = 0; i < in_len; i++) {
628 if (in[i] != out_wstr.at(i)) {
629 err(L"Embedded nulls mishandled in str2wcstring at index %lu", (unsigned long)i);
630 }
631 }
632 }
633
634 /// Test the tokenizer.
test_tokenizer()635 static void test_tokenizer() {
636 say(L"Testing tokenizer");
637 {
638 const wchar_t *str = L"alpha beta";
639 tokenizer_t t(str, 0);
640 maybe_t<tok_t> token{};
641
642 token = t.next(); // alpha
643 do_test(token.has_value());
644 do_test(token->type == token_type_t::string);
645 do_test(token->offset == 0);
646 do_test(token->length == 5);
647 do_test(t.text_of(*token) == L"alpha");
648
649 token = t.next(); // beta
650 do_test(token.has_value());
651 do_test(token->type == token_type_t::string);
652 do_test(token->offset == 6);
653 do_test(token->length == 4);
654 do_test(t.text_of(*token) == L"beta");
655
656 token = t.next();
657 do_test(!token.has_value());
658 }
659
660 const wchar_t *str =
661 L"string <redirection 2>&1 'nested \"quoted\" '(string containing subshells "
662 L"){and,brackets}$as[$well (as variable arrays)] not_a_redirect^ ^ ^^is_a_redirect "
663 L"&| &> "
664 L"&&& ||| "
665 L"&& || & |"
666 L"Compress_Newlines\n \n\t\n \nInto_Just_One";
667 using tt = token_type_t;
668 const token_type_t types[] = {
669 tt::string, tt::redirect, tt::string, tt::redirect, tt::string, tt::string,
670 tt::string, tt::string, tt::string, tt::pipe, tt::redirect, tt::andand,
671 tt::background, tt::oror, tt::pipe, tt::andand, tt::oror, tt::background,
672 tt::pipe, tt::string, tt::end, tt::string};
673
674 say(L"Test correct tokenization");
675
676 {
677 tokenizer_t t(str, 0);
678 size_t i = 0;
679 while (auto token = t.next()) {
680 if (i >= sizeof types / sizeof *types) {
681 err(L"Too many tokens returned from tokenizer");
682 std::fwprintf(stdout, L"Got excess token type %ld\n", (long)token->type);
683 break;
684 }
685 if (types[i] != token->type) {
686 err(L"Tokenization error:");
687 std::fwprintf(
688 stdout,
689 L"Token number %zu of string \n'%ls'\n, expected type %ld, got token type "
690 L"%ld\n",
691 i + 1, str, (long)types[i], (long)token->type);
692 }
693 i++;
694 }
695 if (i < sizeof types / sizeof *types) {
696 err(L"Too few tokens returned from tokenizer");
697 }
698 }
699
700 // Test some errors.
701 {
702 tokenizer_t t(L"abc\\", 0);
703 auto token = t.next();
704 do_test(token.has_value());
705 do_test(token->type == token_type_t::error);
706 do_test(token->error == tokenizer_error_t::unterminated_escape);
707 do_test(token->error_offset_within_token == 3);
708 }
709
710 {
711 tokenizer_t t(L"abc )defg(hij", 0);
712 auto token = t.next();
713 do_test(token.has_value());
714 token = t.next();
715 do_test(token.has_value());
716 do_test(token->type == token_type_t::error);
717 do_test(token->error == tokenizer_error_t::closing_unopened_subshell);
718 do_test(token->offset == 4);
719 do_test(token->error_offset_within_token == 0);
720 }
721
722 {
723 tokenizer_t t(L"abc defg(hij (klm)", 0);
724 auto token = t.next();
725 do_test(token.has_value());
726 token = t.next();
727 do_test(token.has_value());
728 do_test(token->type == token_type_t::error);
729 do_test(token->error == tokenizer_error_t::unterminated_subshell);
730 do_test(token->error_offset_within_token == 4);
731 }
732
733 {
734 tokenizer_t t(L"abc defg[hij (klm)", 0);
735 auto token = t.next();
736 do_test(token.has_value());
737 token = t.next();
738 do_test(token.has_value());
739 do_test(token->type == token_type_t::error);
740 do_test(token->error == tokenizer_error_t::unterminated_slice);
741 do_test(token->error_offset_within_token == 4);
742 }
743
744 // Test some redirection parsing.
745 auto pipe_or_redir = [](const wchar_t *s) { return pipe_or_redir_t::from_string(s); };
746 do_test(pipe_or_redir(L"|")->is_pipe);
747 do_test(pipe_or_redir(L"0>|")->is_pipe);
748 do_test(pipe_or_redir(L"0>|")->fd == 0);
749 do_test(pipe_or_redir(L"2>|")->is_pipe);
750 do_test(pipe_or_redir(L"2>|")->fd == 2);
751 do_test(pipe_or_redir(L">|")->is_pipe);
752 do_test(pipe_or_redir(L">|")->fd == STDOUT_FILENO);
753 do_test(!pipe_or_redir(L">")->is_pipe);
754 do_test(pipe_or_redir(L">")->fd == STDOUT_FILENO);
755 do_test(pipe_or_redir(L"2>")->fd == STDERR_FILENO);
756 do_test(pipe_or_redir(L"9999999999999>")->fd == -1);
757 do_test(pipe_or_redir(L"9999999999999>&2")->fd == -1);
758 do_test(pipe_or_redir(L"9999999999999>&2")->is_valid() == false);
759 do_test(pipe_or_redir(L"9999999999999>&2")->is_valid() == false);
760
761 do_test(pipe_or_redir(L"&|")->is_pipe);
762 do_test(pipe_or_redir(L"&|")->stderr_merge);
763 do_test(!pipe_or_redir(L"&>")->is_pipe);
764 do_test(pipe_or_redir(L"&>")->stderr_merge);
765 do_test(pipe_or_redir(L"&>>")->stderr_merge);
766 do_test(pipe_or_redir(L"&>?")->stderr_merge);
767
768 auto get_redir_mode = [](const wchar_t *s) -> maybe_t<redirection_mode_t> {
769 if (auto redir = pipe_or_redir_t::from_string(s)) {
770 return redir->mode;
771 }
772 return none();
773 };
774
775 if (get_redir_mode(L"<") != redirection_mode_t::input)
776 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
777 if (get_redir_mode(L">") != redirection_mode_t::overwrite)
778 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
779 if (get_redir_mode(L"2>") != redirection_mode_t::overwrite)
780 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
781 if (get_redir_mode(L">>") != redirection_mode_t::append)
782 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
783 if (get_redir_mode(L"2>>") != redirection_mode_t::append)
784 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
785 if (get_redir_mode(L"2>?") != redirection_mode_t::noclob)
786 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
787 if (get_redir_mode(L"9999999999999999>?") != redirection_mode_t::noclob)
788 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
789 if (get_redir_mode(L"2>&3") != redirection_mode_t::fd)
790 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
791 if (get_redir_mode(L"3<&0") != redirection_mode_t::fd)
792 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
793 if (get_redir_mode(L"3</tmp/filetxt") != redirection_mode_t::input)
794 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
795
796 // Test ^ with our feature flag on and off.
797 auto saved_flags = fish_features();
798 mutable_fish_features().set(features_t::stderr_nocaret, false);
799 if (get_redir_mode(L"^") != redirection_mode_t::overwrite)
800 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
801 mutable_fish_features().set(features_t::stderr_nocaret, true);
802 if (get_redir_mode(L"^") != none())
803 err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
804 mutable_fish_features() = saved_flags;
805 }
806
807 // Little function that runs in a background thread, bouncing to the main.
test_iothread_thread_call(std::atomic<int> * addr)808 static int test_iothread_thread_call(std::atomic<int> *addr) {
809 int before = *addr;
810 iothread_perform_on_main([=]() { *addr += 1; });
811 int after = *addr;
812
813 // Must have incremented it at least once.
814 if (before >= after) {
815 err(L"Failed to increment from background thread");
816 }
817 return after;
818 }
819
test_fd_monitor()820 static void test_fd_monitor() {
821 say(L"Testing fd_monitor");
822
823 // Helper to make an item which counts how many times its callback is invoked.
824 struct item_maker_t {
825 std::atomic<bool> did_timeout{false};
826 std::atomic<size_t> length_read{0};
827 std::atomic<size_t> pokes{0};
828 std::atomic<size_t> total_calls{0};
829 fd_monitor_item_id_t item_id{0};
830 bool always_exit{false};
831 fd_monitor_item_t item;
832 autoclose_fd_t writer;
833
834 explicit item_maker_t(uint64_t timeout_usec) {
835 auto pipes = make_autoclose_pipes().acquire();
836 writer = std::move(pipes.write);
837 auto callback = [this](autoclose_fd_t &fd, item_wake_reason_t reason) {
838 bool was_closed = false;
839 switch (reason) {
840 case item_wake_reason_t::timeout:
841 this->did_timeout = true;
842 break;
843 case item_wake_reason_t::poke:
844 this->pokes += 1;
845 break;
846 case item_wake_reason_t::readable:
847 char buff[4096];
848 ssize_t amt = read(fd.fd(), buff, sizeof buff);
849 this->length_read += amt;
850 was_closed = (amt == 0);
851 break;
852 }
853 total_calls += 1;
854 if (always_exit || was_closed) {
855 fd.close();
856 }
857 };
858 item = fd_monitor_item_t(std::move(pipes.read), std::move(callback), timeout_usec);
859 }
860
861 item_maker_t(const item_maker_t &) = delete;
862
863 // Write 42 bytes to our write end.
864 void write42() const {
865 char buff[42] = {0};
866 (void)write_loop(writer.fd(), buff, sizeof buff);
867 }
868 };
869
870 constexpr uint64_t usec_per_msec = 1000;
871
872 // Items which will never receive data or be called back.
873 item_maker_t item_never(fd_monitor_item_t::kNoTimeout);
874 item_maker_t item_hugetimeout(100000000LLU * usec_per_msec);
875
876 // Item which should get no data, and time out.
877 item_maker_t item0_timeout(16 * usec_per_msec);
878
879 // Item which should get exactly 42 bytes, then time out.
880 item_maker_t item42_timeout(16 * usec_per_msec);
881
882 // Item which should get exactly 42 bytes, and not time out.
883 item_maker_t item42_nottimeout(fd_monitor_item_t::kNoTimeout);
884
885 // Item which should get 42 bytes, then get notified it is closed.
886 item_maker_t item42_thenclose(16 * usec_per_msec);
887
888 // Item which gets one poke.
889 item_maker_t item_pokee(fd_monitor_item_t::kNoTimeout);
890
891 // Item which should be called back once.
892 item_maker_t item_oneshot(16 * usec_per_msec);
893 item_oneshot.always_exit = true;
894
895 {
896 fd_monitor_t monitor;
897 for (item_maker_t *item :
898 {&item_never, &item_hugetimeout, &item0_timeout, &item42_timeout, &item42_nottimeout,
899 &item42_thenclose, &item_pokee, &item_oneshot}) {
900 item->item_id = monitor.add(std::move(item->item));
901 }
902 item42_timeout.write42();
903 item42_nottimeout.write42();
904 item42_thenclose.write42();
905 item42_thenclose.writer.close();
906 item_oneshot.write42();
907 monitor.poke_item(item_pokee.item_id);
908
909 // May need to loop here to ensure our fd_monitor gets scheduled - see #7699.
910 for (int i = 0; i < 100; i++) {
911 std::this_thread::sleep_for(std::chrono::milliseconds(84));
912 if (item0_timeout.did_timeout) {
913 break;
914 }
915 }
916 }
917
918 do_test(!item_never.did_timeout);
919 do_test(item_never.length_read == 0);
920 do_test(item_never.pokes == 0);
921
922 do_test(!item_hugetimeout.did_timeout);
923 do_test(item_hugetimeout.length_read == 0);
924 do_test(item_hugetimeout.pokes == 0);
925
926 do_test(item0_timeout.length_read == 0);
927 do_test(item0_timeout.did_timeout);
928 do_test(item0_timeout.pokes == 0);
929
930 do_test(item42_timeout.length_read == 42);
931 do_test(item42_timeout.did_timeout);
932 do_test(item42_timeout.pokes == 0);
933
934 do_test(item42_nottimeout.length_read == 42);
935 do_test(!item42_nottimeout.did_timeout);
936 do_test(item42_nottimeout.pokes == 0);
937
938 do_test(item42_thenclose.did_timeout == false);
939 do_test(item42_thenclose.length_read == 42);
940 do_test(item42_thenclose.total_calls == 2);
941 do_test(item42_thenclose.pokes == 0);
942
943 do_test(!item_oneshot.did_timeout);
944 do_test(item_oneshot.length_read == 42);
945 do_test(item_oneshot.total_calls == 1);
946 do_test(item_oneshot.pokes == 0);
947
948 do_test(!item_pokee.did_timeout);
949 do_test(item_pokee.length_read == 0);
950 do_test(item_pokee.total_calls == 1);
951 do_test(item_pokee.pokes == 1);
952 }
953
test_iothread()954 static void test_iothread() {
955 say(L"Testing iothreads");
956 std::unique_ptr<std::atomic<int>> int_ptr = make_unique<std::atomic<int>>(0);
957 int iterations = 64;
958 for (int i = 0; i < iterations; i++) {
959 iothread_perform([&]() { test_iothread_thread_call(int_ptr.get()); });
960 }
961 iothread_drain_all();
962
963 // Should have incremented it once per thread.
964 do_test(*int_ptr == iterations);
965 if (*int_ptr != iterations) {
966 say(L"Expected int to be %d, but instead it was %d", iterations, int_ptr->load());
967 }
968 }
969
test_pthread()970 static void test_pthread() {
971 say(L"Testing pthreads");
972 std::atomic<int> val{3};
973 std::promise<void> promise;
974 bool made = make_detached_pthread([&]() {
975 val = val + 2;
976 promise.set_value();
977 });
978 do_test(made);
979 promise.get_future().wait();
980 do_test(val == 5);
981 }
982
test_debounce()983 static void test_debounce() {
984 say(L"Testing debounce");
985 // Run 8 functions using a condition variable.
986 // Only the first and last should run.
987 debounce_t db;
988 constexpr size_t count = 8;
989 std::array<bool, count> handler_ran = {};
990 std::array<bool, count> completion_ran = {};
991
992 bool ready_to_go = false;
993 std::mutex m;
994 std::condition_variable cv;
995
996 // "Enqueue" all functions. Each one waits until ready_to_go.
997 for (size_t idx = 0; idx < count; idx++) {
998 do_test(handler_ran[idx] == false);
999 db.perform(
1000 [&, idx] {
1001 std::unique_lock<std::mutex> lock(m);
1002 cv.wait(lock, [&] { return ready_to_go; });
1003 handler_ran[idx] = true;
1004 return idx;
1005 },
1006 [&](size_t idx) { completion_ran[idx] = true; });
1007 }
1008
1009 // We're ready to go.
1010 {
1011 std::unique_lock<std::mutex> lock(m);
1012 ready_to_go = true;
1013 }
1014 cv.notify_all();
1015
1016 // Wait until the last completion is done.
1017 while (!completion_ran.back()) {
1018 iothread_service_main();
1019 }
1020 iothread_drain_all();
1021
1022 // Each perform() call may displace an existing queued operation.
1023 // Each operation waits until all are queued.
1024 // Therefore we expect the last perform() to have run, and at most one more.
1025
1026 do_test(handler_ran.back());
1027 do_test(completion_ran.back());
1028
1029 size_t total_ran = 0;
1030 for (size_t idx = 0; idx < count; idx++) {
1031 total_ran += (handler_ran[idx] ? 1 : 0);
1032 do_test(handler_ran[idx] == completion_ran[idx]);
1033 }
1034 do_test(total_ran <= 2);
1035 }
1036
test_debounce_timeout()1037 static void test_debounce_timeout() {
1038 using namespace std::chrono;
1039 say(L"Testing debounce timeout");
1040
1041 // Verify that debounce doesn't wait forever.
1042 // Use a shared_ptr so we don't have to join our threads.
1043 const long timeout_ms = 50;
1044 struct data_t {
1045 debounce_t db{timeout_ms};
1046 bool exit_ok = false;
1047 std::mutex m;
1048 std::condition_variable cv;
1049 relaxed_atomic_t<uint32_t> running{0};
1050 };
1051 auto data = std::make_shared<data_t>();
1052
1053 // Our background handler. Note this just blocks until exit_ok is set.
1054 std::function<void()> handler = [data] {
1055 data->running++;
1056 std::unique_lock<std::mutex> lock(data->m);
1057 data->cv.wait(lock, [&] { return data->exit_ok; });
1058 };
1059
1060 // Spawn the handler twice. This should not modify the thread token.
1061 uint64_t token1 = data->db.perform(handler);
1062 uint64_t token2 = data->db.perform(handler);
1063 do_test(token1 == token2);
1064
1065 // Wait 75 msec, then enqueue something else; this should spawn a new thread.
1066 std::this_thread::sleep_for(std::chrono::milliseconds(timeout_ms + timeout_ms / 2));
1067 do_test(data->running == 1);
1068 uint64_t token3 = data->db.perform(handler);
1069 do_test(token3 > token2);
1070
1071 // Release all the threads.
1072 std::unique_lock<std::mutex> lock(data->m);
1073 data->exit_ok = true;
1074 data->cv.notify_all();
1075 }
1076
detect_argument_errors(const wcstring & src)1077 static parser_test_error_bits_t detect_argument_errors(const wcstring &src) {
1078 using namespace ast;
1079 auto ast = ast_t::parse_argument_list(src, parse_flag_none);
1080 if (ast.errored()) {
1081 return PARSER_TEST_ERROR;
1082 }
1083 const ast::argument_t *first_arg =
1084 ast.top()->as<freestanding_argument_list_t>()->arguments.at(0);
1085 if (!first_arg) {
1086 err(L"Failed to parse an argument");
1087 return 0;
1088 }
1089 return parse_util_detect_errors_in_argument(*first_arg, first_arg->source(src));
1090 }
1091
1092 /// Test the parser.
test_parser()1093 static void test_parser() {
1094 say(L"Testing parser");
1095
1096 auto detect_errors = [](const wcstring &s) {
1097 return parse_util_detect_errors(s, nullptr, true /* accept incomplete */);
1098 };
1099
1100 say(L"Testing block nesting");
1101 if (!detect_errors(L"if; end")) {
1102 err(L"Incomplete if statement undetected");
1103 }
1104 if (!detect_errors(L"if test; echo")) {
1105 err(L"Missing end undetected");
1106 }
1107 if (!detect_errors(L"if test; end; end")) {
1108 err(L"Unbalanced end undetected");
1109 }
1110
1111 say(L"Testing detection of invalid use of builtin commands");
1112 if (!detect_errors(L"case foo")) {
1113 err(L"'case' command outside of block context undetected");
1114 }
1115 if (!detect_errors(L"switch ggg; if true; case foo;end;end")) {
1116 err(L"'case' command outside of switch block context undetected");
1117 }
1118 if (!detect_errors(L"else")) {
1119 err(L"'else' command outside of conditional block context undetected");
1120 }
1121 if (!detect_errors(L"else if")) {
1122 err(L"'else if' command outside of conditional block context undetected");
1123 }
1124 if (!detect_errors(L"if false; else if; end")) {
1125 err(L"'else if' missing command undetected");
1126 }
1127
1128 if (!detect_errors(L"break")) {
1129 err(L"'break' command outside of loop block context undetected");
1130 }
1131
1132 if (detect_errors(L"break --help")) {
1133 err(L"'break --help' incorrectly marked as error");
1134 }
1135
1136 if (!detect_errors(L"while false ; function foo ; break ; end ; end ")) {
1137 err(L"'break' command inside function allowed to break from loop outside it");
1138 }
1139
1140 if (!detect_errors(L"exec ls|less") || !detect_errors(L"echo|return")) {
1141 err(L"Invalid pipe command undetected");
1142 }
1143
1144 if (detect_errors(L"for i in foo ; switch $i ; case blah ; break; end; end ")) {
1145 err(L"'break' command inside switch falsely reported as error");
1146 }
1147
1148 if (detect_errors(L"or cat | cat") || detect_errors(L"and cat | cat")) {
1149 err(L"boolean command at beginning of pipeline falsely reported as error");
1150 }
1151
1152 if (!detect_errors(L"cat | and cat")) {
1153 err(L"'and' command in pipeline not reported as error");
1154 }
1155
1156 if (!detect_errors(L"cat | or cat")) {
1157 err(L"'or' command in pipeline not reported as error");
1158 }
1159
1160 if (!detect_errors(L"cat | exec") || !detect_errors(L"exec | cat")) {
1161 err(L"'exec' command in pipeline not reported as error");
1162 }
1163
1164 if (!detect_errors(L"begin ; end arg")) {
1165 err(L"argument to 'end' not reported as error");
1166 }
1167
1168 if (!detect_errors(L"switch foo ; end arg")) {
1169 err(L"argument to 'end' not reported as error");
1170 }
1171
1172 if (!detect_errors(L"if true; else if false ; end arg")) {
1173 err(L"argument to 'end' not reported as error");
1174 }
1175
1176 if (!detect_errors(L"if true; else ; end arg")) {
1177 err(L"argument to 'end' not reported as error");
1178 }
1179
1180 if (detect_errors(L"begin ; end 2> /dev/null")) {
1181 err(L"redirection after 'end' wrongly reported as error");
1182 }
1183
1184 if (detect_errors(L"true | ") != PARSER_TEST_INCOMPLETE) {
1185 err(L"unterminated pipe not reported properly");
1186 }
1187
1188 if (detect_errors(L"echo (\nfoo\n bar") != PARSER_TEST_INCOMPLETE) {
1189 err(L"unterminated multiline subshell not reported properly");
1190 }
1191
1192 if (detect_errors(L"begin ; true ; end | ") != PARSER_TEST_INCOMPLETE) {
1193 err(L"unterminated pipe not reported properly");
1194 }
1195
1196 if (detect_errors(L" | true ") != PARSER_TEST_ERROR) {
1197 err(L"leading pipe not reported properly");
1198 }
1199
1200 if (detect_errors(L"true | # comment") != PARSER_TEST_INCOMPLETE) {
1201 err(L"comment after pipe not reported as incomplete");
1202 }
1203
1204 if (detect_errors(L"true | # comment \n false ")) {
1205 err(L"comment and newline after pipe wrongly reported as error");
1206 }
1207
1208 if (detect_errors(L"true | ; false ") != PARSER_TEST_ERROR) {
1209 err(L"semicolon after pipe not detected as error");
1210 }
1211
1212 if (detect_argument_errors(L"foo")) {
1213 err(L"simple argument reported as error");
1214 }
1215
1216 if (detect_argument_errors(L"''")) {
1217 err(L"Empty string reported as error");
1218 }
1219
1220 if (!(detect_argument_errors(L"foo$$") & PARSER_TEST_ERROR)) {
1221 err(L"Bad variable expansion not reported as error");
1222 }
1223
1224 if (!(detect_argument_errors(L"foo$@") & PARSER_TEST_ERROR)) {
1225 err(L"Bad variable expansion not reported as error");
1226 }
1227
1228 // Within command substitutions, we should be able to detect everything that
1229 // parse_util_detect_errors can detect.
1230 if (!(detect_argument_errors(L"foo(cat | or cat)") & PARSER_TEST_ERROR)) {
1231 err(L"Bad command substitution not reported as error");
1232 }
1233
1234 if (!(detect_argument_errors(L"foo\\xFF9") & PARSER_TEST_ERROR)) {
1235 err(L"Bad escape not reported as error");
1236 }
1237
1238 if (!(detect_argument_errors(L"foo(echo \\xFF9)") & PARSER_TEST_ERROR)) {
1239 err(L"Bad escape in command substitution not reported as error");
1240 }
1241
1242 if (!(detect_argument_errors(L"foo(echo (echo (echo \\xFF9)))") & PARSER_TEST_ERROR)) {
1243 err(L"Bad escape in nested command substitution not reported as error");
1244 }
1245
1246 if (!detect_errors(L"false & ; and cat")) {
1247 err(L"'and' command after background not reported as error");
1248 }
1249
1250 if (!detect_errors(L"true & ; or cat")) {
1251 err(L"'or' command after background not reported as error");
1252 }
1253
1254 if (detect_errors(L"true & ; not cat")) {
1255 err(L"'not' command after background falsely reported as error");
1256 }
1257
1258 if (!detect_errors(L"if true & ; end")) {
1259 err(L"backgrounded 'if' conditional not reported as error");
1260 }
1261
1262 if (!detect_errors(L"if false; else if true & ; end")) {
1263 err(L"backgrounded 'else if' conditional not reported as error");
1264 }
1265
1266 if (!detect_errors(L"while true & ; end")) {
1267 err(L"backgrounded 'while' conditional not reported as error");
1268 }
1269
1270 if (!detect_errors(L"true | || false")) {
1271 err(L"bogus boolean statement error not detected on line %d", __LINE__);
1272 }
1273
1274 if (!detect_errors(L"|| false")) {
1275 err(L"bogus boolean statement error not detected on line %d", __LINE__);
1276 }
1277
1278 if (!detect_errors(L"&& false")) {
1279 err(L"bogus boolean statement error not detected on line %d", __LINE__);
1280 }
1281
1282 if (!detect_errors(L"true ; && false")) {
1283 err(L"bogus boolean statement error not detected on line %d", __LINE__);
1284 }
1285
1286 if (!detect_errors(L"true ; || false")) {
1287 err(L"bogus boolean statement error not detected on line %d", __LINE__);
1288 }
1289
1290 if (!detect_errors(L"true || && false")) {
1291 err(L"bogus boolean statement error not detected on line %d", __LINE__);
1292 }
1293
1294 if (!detect_errors(L"true && || false")) {
1295 err(L"bogus boolean statement error not detected on line %d", __LINE__);
1296 }
1297
1298 if (!detect_errors(L"true && && false")) {
1299 err(L"bogus boolean statement error not detected on line %d", __LINE__);
1300 }
1301
1302 if (detect_errors(L"true && ") != PARSER_TEST_INCOMPLETE) {
1303 err(L"unterminated conjunction not reported properly");
1304 }
1305
1306 if (detect_errors(L"true && \n true")) {
1307 err(L"newline after && reported as error");
1308 }
1309
1310 if (detect_errors(L"true || \n") != PARSER_TEST_INCOMPLETE) {
1311 err(L"unterminated conjunction not reported properly");
1312 }
1313
1314 say(L"Testing basic evaluation");
1315
1316 // Ensure that we don't crash on infinite self recursion and mutual recursion. These must use
1317 // the principal parser because we cannot yet execute jobs on other parsers.
1318 auto parser = parser_t::principal_parser().shared();
1319 say(L"Testing recursion detection");
1320 parser->eval(L"function recursive ; recursive ; end ; recursive; ", io_chain_t());
1321
1322 parser->eval(
1323 L"function recursive1 ; recursive2 ; end ; "
1324 L"function recursive2 ; recursive1 ; end ; recursive1; ",
1325 io_chain_t());
1326
1327 say(L"Testing empty function name");
1328 parser->eval(L"function '' ; echo fail; exit 42 ; end ; ''", io_chain_t());
1329
1330 say(L"Testing eval_args");
1331 completion_list_t comps = parser_t::expand_argument_list(L"alpha 'beta gamma' delta",
1332 expand_flags_t{}, parser->context());
1333 do_test(comps.size() == 3);
1334 do_test(comps.at(0).completion == L"alpha");
1335 do_test(comps.at(1).completion == L"beta gamma");
1336 do_test(comps.at(2).completion == L"delta");
1337 }
1338
test_1_cancellation(const wchar_t * src)1339 static void test_1_cancellation(const wchar_t *src) {
1340 auto filler = io_bufferfill_t::create();
1341 pthread_t thread = pthread_self();
1342 double delay = 0.50 /* seconds */;
1343 iothread_perform([=]() {
1344 /// Wait a while and then SIGINT the main thread.
1345 usleep(delay * 1E6);
1346 pthread_kill(thread, SIGINT);
1347 });
1348 eval_res_t res = parser_t::principal_parser().eval(src, io_chain_t{filler});
1349 separated_buffer_t buffer = io_bufferfill_t::finish(std::move(filler));
1350 if (buffer.size() != 0) {
1351 err(L"Expected 0 bytes in out_buff, but instead found %lu bytes, for command %ls\n",
1352 buffer.size(), src);
1353 }
1354 do_test(res.status.signal_exited() && res.status.signal_code() == SIGINT);
1355 iothread_drain_all();
1356 }
1357
test_cancellation()1358 static void test_cancellation() {
1359 say(L"Testing Ctrl-C cancellation. If this hangs, that's a bug!");
1360
1361 // Enable fish's signal handling here.
1362 signal_set_handlers(true);
1363
1364 // This tests that we can correctly ctrl-C out of certain loop constructs, and that nothing gets
1365 // printed if we do.
1366
1367 // Here the command substitution is an infinite loop. echo never even gets its argument, so when
1368 // we cancel we expect no output.
1369 test_1_cancellation(L"echo (while true ; echo blah ; end)");
1370
1371 // Nasty infinite loop that doesn't actually execute anything.
1372 test_1_cancellation(L"echo (while true ; end) (while true ; end) (while true ; end)");
1373 test_1_cancellation(L"while true ; end");
1374 test_1_cancellation(L"while true ; echo nothing > /dev/null; end");
1375 test_1_cancellation(L"for i in (while true ; end) ; end");
1376
1377 signal_reset_handlers();
1378
1379 // Ensure that we don't think we should cancel.
1380 reader_reset_interrupted();
1381 signal_clear_cancel();
1382 }
1383
1384 namespace indent_tests {
1385 // A struct which is either text or a new indent.
1386 struct segment_t {
1387 // The indent to set
1388 int indent{0};
1389 const char *text{nullptr};
1390
segment_tindent_tests::segment_t1391 /* implicit */ segment_t(int indent) : indent(indent) {}
segment_tindent_tests::segment_t1392 /* implicit */ segment_t(const char *text) : text(text) {}
1393 };
1394
1395 using test_t = std::vector<segment_t>;
1396 using test_list_t = std::vector<test_t>;
1397
1398 // Add a new test to a test list based on a series of ints and texts.
1399 template <typename... Types>
add_test(test_list_t * v,const Types &...types)1400 void add_test(test_list_t *v, const Types &...types) {
1401 segment_t segments[] = {types...};
1402 v->emplace_back(std::begin(segments), std::end(segments));
1403 }
1404 } // namespace indent_tests
1405
test_indents()1406 static void test_indents() {
1407 say(L"Testing indents");
1408 using namespace indent_tests;
1409
1410 test_list_t tests;
1411 add_test(&tests, //
1412 0, "if", 1, " foo", //
1413 0, "\nend");
1414
1415 add_test(&tests, //
1416 0, "if", 1, " foo", //
1417 1, "\nfoo", //
1418 0, "\nend");
1419
1420 add_test(&tests, //
1421 0, "if", 1, " foo", //
1422 1, "\nif", 2, " bar", //
1423 1, "\nend", //
1424 0, "\nend");
1425
1426 add_test(&tests, //
1427 0, "if", 1, " foo", //
1428 1, "\nif", 2, " bar", //
1429 2, "\n", //
1430 1, "\nend\n");
1431
1432 add_test(&tests, //
1433 0, "if", 1, " foo", //
1434 1, "\nif", 2, " bar", //
1435 2, "\n");
1436
1437 add_test(&tests, //
1438 0, "begin", //
1439 1, "\nfoo", //
1440 1, "\n");
1441
1442 add_test(&tests, //
1443 0, "begin", //
1444 1, "\n;", //
1445 0, "end", //
1446 0, "\nfoo", 0, "\n");
1447
1448 add_test(&tests, //
1449 0, "begin", //
1450 1, "\n;", //
1451 0, "end", //
1452 0, "\nfoo", 0, "\n");
1453
1454 add_test(&tests, //
1455 0, "if", 1, " foo", //
1456 1, "\nif", 2, " bar", //
1457 2, "\nbaz", //
1458 1, "\nend", 1, "\n");
1459
1460 add_test(&tests, //
1461 0, "switch foo", //
1462 1, "\n" //
1463 );
1464
1465 add_test(&tests, //
1466 0, "switch foo", //
1467 1, "\ncase bar", //
1468 1, "\ncase baz", //
1469 2, "\nquux", //
1470 2, "\nquux" //
1471 );
1472
1473 add_test(&tests, //
1474 0, "switch foo", //
1475 1, "\ncas" // parse error indentation handling
1476 );
1477
1478 add_test(&tests, //
1479 0, "while", 1, " false", //
1480 1, "\n# comment", // comment indentation handling
1481 1, "\ncommand", //
1482 1, "\n# comment 2" //
1483 );
1484
1485 add_test(&tests, //
1486 0, "begin", //
1487 1, "\n", // "begin" is special because this newline belongs to the block header
1488 1, "\n" //
1489 );
1490
1491 // Continuation lines.
1492 add_test(&tests, //
1493 0, "echo 'continuation line' \\", //
1494 1, "\ncont", //
1495 0, "\n" //
1496 );
1497 add_test(&tests, //
1498 0, "echo 'empty continuation line' \\", //
1499 1, "\n" //
1500 );
1501 add_test(&tests, //
1502 0, "begin # continuation line in block", //
1503 1, "\necho \\", //
1504 2, "\ncont" //
1505 );
1506 add_test(&tests, //
1507 0, "begin # empty continuation line in block", //
1508 1, "\necho \\", //
1509 2, "\n", //
1510 0, "\nend" //
1511 );
1512 add_test(&tests, //
1513 0, "echo 'multiple continuation lines' \\", //
1514 1, "\nline1 \\", //
1515 1, "\n# comment", //
1516 1, "\n# more comment", //
1517 1, "\nline2 \\", //
1518 1, "\n" //
1519 );
1520 add_test(&tests, //
1521 0, "echo # inline comment ending in \\", //
1522 0, "\nline" //
1523 );
1524 add_test(&tests, //
1525 0, "# line comment ending in \\", //
1526 0, "\nline" //
1527 );
1528 add_test(&tests, //
1529 0, "echo 'multiple empty continuation lines' \\", //
1530 1, "\n\\", //
1531 1, "\n", //
1532 0, "\n" //
1533 );
1534 add_test(&tests, //
1535 0, "echo 'multiple statements with continuation lines' \\", //
1536 1, "\nline 1", //
1537 0, "\necho \\", //
1538 1, "\n" //
1539 );
1540 // This is an edge case, probably okay to change the behavior here.
1541 add_test(&tests, //
1542 0, "begin", 1, " \\", //
1543 2, "\necho 'continuation line in block header' \\", //
1544 2, "\n", //
1545 1, "\n", //
1546 0, "\nend" //
1547 );
1548
1549 int test_idx = 0;
1550 for (const test_t &test : tests) {
1551 // Construct the input text and expected indents.
1552 wcstring text;
1553 std::vector<int> expected_indents;
1554 int current_indent = 0;
1555 for (const segment_t &segment : test) {
1556 if (!segment.text) {
1557 current_indent = segment.indent;
1558 } else {
1559 wcstring tmp = str2wcstring(segment.text);
1560 text.append(tmp);
1561 expected_indents.insert(expected_indents.end(), tmp.size(), current_indent);
1562 }
1563 }
1564 do_test(expected_indents.size() == text.size());
1565
1566 // Compute the indents.
1567 std::vector<int> indents = parse_util_compute_indents(text);
1568
1569 if (expected_indents.size() != indents.size()) {
1570 err(L"Indent vector has wrong size! Expected %lu, actual %lu", expected_indents.size(),
1571 indents.size());
1572 }
1573 do_test(expected_indents.size() == indents.size());
1574 for (size_t i = 0; i < text.size(); i++) {
1575 if (expected_indents.at(i) != indents.at(i)) {
1576 err(L"Wrong indent at index %lu (char 0x%02x) in test #%lu (expected %d, actual "
1577 L"%d):\n%ls\n",
1578 i, text.at(i), test_idx, expected_indents.at(i), indents.at(i), text.c_str());
1579 break; // don't keep showing errors for the rest of the test
1580 }
1581 }
1582 test_idx++;
1583 }
1584 }
1585
test_parse_util_cmdsubst_extent()1586 static void test_parse_util_cmdsubst_extent() {
1587 const wchar_t *a = L"echo (echo (echo hi";
1588 const wchar_t *begin = NULL, *end = NULL;
1589
1590 parse_util_cmdsubst_extent(a, 0, &begin, &end);
1591 if (begin != a || end != begin + std::wcslen(begin)) {
1592 err(L"parse_util_cmdsubst_extent failed on line %ld", (long)__LINE__);
1593 }
1594 parse_util_cmdsubst_extent(a, 1, &begin, &end);
1595 if (begin != a || end != begin + std::wcslen(begin)) {
1596 err(L"parse_util_cmdsubst_extent failed on line %ld", (long)__LINE__);
1597 }
1598 parse_util_cmdsubst_extent(a, 2, &begin, &end);
1599 if (begin != a || end != begin + std::wcslen(begin)) {
1600 err(L"parse_util_cmdsubst_extent failed on line %ld", (long)__LINE__);
1601 }
1602 parse_util_cmdsubst_extent(a, 3, &begin, &end);
1603 if (begin != a || end != begin + std::wcslen(begin)) {
1604 err(L"parse_util_cmdsubst_extent failed on line %ld", (long)__LINE__);
1605 }
1606
1607 parse_util_cmdsubst_extent(a, 8, &begin, &end);
1608 if (begin != a + const_strlen(L"echo (")) {
1609 err(L"parse_util_cmdsubst_extent failed on line %ld", (long)__LINE__);
1610 }
1611
1612 parse_util_cmdsubst_extent(a, 17, &begin, &end);
1613 if (begin != a + const_strlen(L"echo (echo (")) {
1614 err(L"parse_util_cmdsubst_extent failed on line %ld", (long)__LINE__);
1615 }
1616 }
1617
1618 static struct wcsfilecmp_test {
1619 const wchar_t *str1;
1620 const wchar_t *str2;
1621 int expected_rc;
1622 } wcsfilecmp_tests[] = {{L"", L"", 0},
1623 {L"", L"def", -1},
1624 {L"abc", L"", 1},
1625 {L"abc", L"def", -1},
1626 {L"abc", L"DEF", -1},
1627 {L"DEF", L"abc", 1},
1628 {L"abc", L"abc", 0},
1629 {L"ABC", L"ABC", 0},
1630 {L"AbC", L"abc", -1},
1631 {L"AbC", L"ABC", 1},
1632 {L"def", L"abc", 1},
1633 {L"1ghi", L"1gHi", 1},
1634 {L"1ghi", L"2ghi", -1},
1635 {L"1ghi", L"01ghi", 1},
1636 {L"1ghi", L"02ghi", -1},
1637 {L"01ghi", L"1ghi", -1},
1638 {L"1ghi", L"002ghi", -1},
1639 {L"002ghi", L"1ghi", 1},
1640 {L"abc01def", L"abc1def", -1},
1641 {L"abc1def", L"abc01def", 1},
1642 {L"abc12", L"abc5", 1},
1643 {L"51abc", L"050abc", 1},
1644 {L"abc5", L"abc12", -1},
1645 {L"5abc", L"12ABC", -1},
1646 {L"abc0789", L"abc789", -1},
1647 {L"abc0xA789", L"abc0xA0789", 1},
1648 {L"abc002", L"abc2", -1},
1649 {L"abc002g", L"abc002", 1},
1650 {L"abc002g", L"abc02g", -1},
1651 {L"abc002.txt", L"abc02.txt", -1},
1652 {L"abc005", L"abc012", -1},
1653 {L"abc02", L"abc002", 1},
1654 {L"abc002.txt", L"abc02.txt", -1},
1655 {L"GHI1abc2.txt", L"ghi1abc2.txt", -1},
1656 {L"a0", L"a00", -1},
1657 {L"a00b", L"a0b", -1},
1658 {L"a0b", L"a00b", 1},
1659 {L"a-b", L"azb", 1},
1660 {NULL, NULL, 0}};
1661
1662 /// Verify the behavior of the `wcsfilecmp()` function.
test_wcsfilecmp()1663 static void test_wcsfilecmp() {
1664 for (auto test = wcsfilecmp_tests; test->str1; test++) {
1665 int rc = wcsfilecmp(test->str1, test->str2);
1666 if (rc != test->expected_rc) {
1667 err(L"New failed on line %lu: [\"%ls\" <=> \"%ls\"]: "
1668 L"expected return code %d but got %d",
1669 __LINE__, test->str1, test->str2, test->expected_rc, rc);
1670 }
1671 }
1672 }
1673
test_utility_functions()1674 static void test_utility_functions() {
1675 say(L"Testing utility functions");
1676 test_wcsfilecmp();
1677 test_parse_util_cmdsubst_extent();
1678 }
1679
1680 // UTF8 tests taken from Alexey Vatchenko's utf8 library. See http://www.bsdua.org/libbsdua.html.
test_utf82wchar(const char * src,size_t slen,const wchar_t * dst,size_t dlen,int flags,size_t res,const char * descr)1681 static void test_utf82wchar(const char *src, size_t slen, const wchar_t *dst, size_t dlen,
1682 int flags, size_t res, const char *descr) {
1683 size_t size;
1684 wchar_t *mem = NULL;
1685
1686 #if WCHAR_MAX == 0xffff
1687 // Hack: if wchar is only UCS-2, and the UTF-8 input string contains astral characters, then
1688 // tweak the expected size to 0.
1689 if (src) {
1690 // A UTF-8 code unit may represent an astral code point if it has 4 or more leading 1s.
1691 const unsigned char astral_mask = 0xF0;
1692 for (size_t i = 0; i < slen; i++) {
1693 if ((src[i] & astral_mask) == astral_mask) {
1694 // Astral char. We want this conversion to fail.
1695 res = 0; //!OCLINT(parameter reassignment)
1696 break;
1697 }
1698 }
1699 }
1700 #endif
1701
1702 if (!dst) {
1703 size = utf8_to_wchar(src, slen, NULL, flags);
1704 } else {
1705 mem = (wchar_t *)malloc(dlen * sizeof(*mem));
1706 if (!mem) {
1707 err(L"u2w: %s: MALLOC FAILED\n", descr);
1708 return;
1709 }
1710
1711 std::wstring buff;
1712 size = utf8_to_wchar(src, slen, &buff, flags);
1713 std::copy(buff.begin(), buff.begin() + std::min(dlen, buff.size()), mem);
1714 }
1715
1716 if (res != size) {
1717 err(L"u2w: %s: FAILED (rv: %lu, must be %lu)", descr, size, res);
1718 } else if (mem && std::memcmp(mem, dst, size * sizeof(*mem)) != 0) {
1719 err(L"u2w: %s: BROKEN", descr);
1720 }
1721
1722 free(mem);
1723 }
1724
1725 // Annoying variant to handle uchar to avoid narrowing conversion warnings.
test_utf82wchar(const unsigned char * usrc,size_t slen,const wchar_t * dst,size_t dlen,int flags,size_t res,const char * descr)1726 static void test_utf82wchar(const unsigned char *usrc, size_t slen, const wchar_t *dst, size_t dlen,
1727 int flags, size_t res, const char *descr) {
1728 const char *src = reinterpret_cast<const char *>(usrc);
1729 return test_utf82wchar(src, slen, dst, dlen, flags, res, descr);
1730 }
1731
test_wchar2utf8(const wchar_t * src,size_t slen,const char * dst,size_t dlen,int flags,size_t res,const char * descr)1732 static void test_wchar2utf8(const wchar_t *src, size_t slen, const char *dst, size_t dlen,
1733 int flags, size_t res, const char *descr) {
1734 size_t size;
1735 char *mem = NULL;
1736
1737 #if WCHAR_MAX == 0xffff
1738 // Hack: if wchar is simulating UCS-2, and the wchar_t input string contains astral characters,
1739 // then tweak the expected size to 0.
1740 if (src) {
1741 const uint32_t astral_mask = 0xFFFF0000U;
1742 for (size_t i = 0; i < slen; i++) {
1743 if ((src[i] & astral_mask) != 0) {
1744 // Astral char. We want this conversion to fail.
1745 res = 0; //!OCLINT(parameter reassignment)
1746 break;
1747 }
1748 }
1749 }
1750 #endif
1751
1752 if (dst) {
1753 // We want to pass a valid pointer to wchar_to_utf8, so allocate at least one byte.
1754 mem = (char *)malloc(dlen + 1);
1755 if (!mem) {
1756 err(L"w2u: %s: MALLOC FAILED", descr);
1757 return;
1758 }
1759 }
1760
1761 size = wchar_to_utf8(src, slen, mem, dlen, flags);
1762 if (res != size) {
1763 err(L"w2u: %s: FAILED (rv: %lu, must be %lu)", descr, size, res);
1764 } else if (dst && std::memcmp(mem, dst, size) != 0) {
1765 err(L"w2u: %s: BROKEN", descr);
1766 }
1767
1768 free(mem);
1769 }
1770
1771 // Annoying variant to handle uchar to avoid narrowing conversion warnings.
test_wchar2utf8(const wchar_t * src,size_t slen,const unsigned char * udst,size_t dlen,int flags,size_t res,const char * descr)1772 static void test_wchar2utf8(const wchar_t *src, size_t slen, const unsigned char *udst, size_t dlen,
1773 int flags, size_t res, const char *descr) {
1774 const char *dst = reinterpret_cast<const char *>(udst);
1775 return test_wchar2utf8(src, slen, dst, dlen, flags, res, descr);
1776 }
1777
test_utf8()1778 static void test_utf8() {
1779 say(L"Testing utf8");
1780 wchar_t w1[] = {0x54, 0x65, 0x73, 0x74};
1781 wchar_t w2[] = {0x0422, 0x0435, 0x0441, 0x0442};
1782 wchar_t w3[] = {0x800, 0x1e80, 0x98c4, 0x9910, 0xff00};
1783 wchar_t wm[] = {0x41, 0x0441, 0x3042, 0xff67, 0x9b0d};
1784 wchar_t wb2[] = {0xd800, 0xda00, 0x41, 0xdfff, 0x0a};
1785 wchar_t wbom[] = {0xfeff, 0x41, 0x0a};
1786 wchar_t wbom2[] = {0x41, 0xa};
1787 wchar_t wbom22[] = {0xfeff, 0x41, 0x0a};
1788 unsigned char u1[] = {0x54, 0x65, 0x73, 0x74};
1789 unsigned char u2[] = {0xd0, 0xa2, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82};
1790 unsigned char u3[] = {0xe0, 0xa0, 0x80, 0xe1, 0xba, 0x80, 0xe9, 0xa3,
1791 0x84, 0xe9, 0xa4, 0x90, 0xef, 0xbc, 0x80};
1792 unsigned char um[] = {0x41, 0xd1, 0x81, 0xe3, 0x81, 0x82, 0xef, 0xbd, 0xa7, 0xe9, 0xac, 0x8d};
1793 unsigned char uc080[] = {0xc0, 0x80};
1794 unsigned char ub2[] = {0xed, 0xa1, 0x8c, 0xed, 0xbe, 0xb4, 0x0a};
1795 unsigned char ubom[] = {0x41, 0xa};
1796 unsigned char ubom2[] = {0xef, 0xbb, 0xbf, 0x41, 0x0a};
1797 #if WCHAR_MAX != 0xffff
1798 wchar_t w4[] = {0x15555, 0xf7777, 0x0a};
1799 wchar_t wb[] = {(wchar_t)-2, 0xa, (wchar_t)0xffffffff, 0x0441};
1800 wchar_t wb1[] = {0x0a, 0x0422};
1801 unsigned char u4[] = {0xf0, 0x95, 0x95, 0x95, 0xf3, 0xb7, 0x9d, 0xb7, 0x0a};
1802 unsigned char ub[] = {0xa, 0xd1, 0x81};
1803 unsigned char ub1[] = {0xa, 0xff, 0xd0, 0xa2, 0xfe, 0x8f, 0xe0, 0x80};
1804 #endif
1805
1806 // UTF-8 -> UCS-4 string.
1807 test_utf82wchar(ubom2, sizeof(ubom2), wbom2, sizeof(wbom2) / sizeof(*wbom2), UTF8_SKIP_BOM,
1808 sizeof(wbom2) / sizeof(*wbom2), "ubom2 skip BOM");
1809 test_utf82wchar(ubom2, sizeof(ubom2), wbom22, sizeof(wbom22) / sizeof(*wbom22), 0,
1810 sizeof(wbom22) / sizeof(*wbom22), "ubom2 BOM");
1811 test_utf82wchar(uc080, sizeof(uc080), NULL, 0, 0, 0, "uc080 c0 80 - forbitten by rfc3629");
1812 test_utf82wchar(ub2, sizeof(ub2), NULL, 0, 0, 3, "ub2 resulted in forbitten wchars (len)");
1813 test_utf82wchar(ub2, sizeof(ub2), wb2, sizeof(wb2) / sizeof(*wb2), 0, 0,
1814 "ub2 resulted in forbitten wchars");
1815 test_utf82wchar(ub2, sizeof(ub2), L"\x0a", 1, UTF8_IGNORE_ERROR, 1,
1816 "ub2 resulted in ignored forbitten wchars");
1817 test_utf82wchar(u1, sizeof(u1), w1, sizeof(w1) / sizeof(*w1), 0, sizeof(w1) / sizeof(*w1),
1818 "u1/w1 1 octet chars");
1819 test_utf82wchar(u2, sizeof(u2), w2, sizeof(w2) / sizeof(*w2), 0, sizeof(w2) / sizeof(*w2),
1820 "u2/w2 2 octets chars");
1821 test_utf82wchar(u3, sizeof(u3), w3, sizeof(w3) / sizeof(*w3), 0, sizeof(w3) / sizeof(*w3),
1822 "u3/w3 3 octets chars");
1823 test_utf82wchar("\xff", 1, NULL, 0, 0, 0, "broken utf-8 0xff symbol");
1824 test_utf82wchar("\xfe", 1, NULL, 0, 0, 0, "broken utf-8 0xfe symbol");
1825 test_utf82wchar("\x8f", 1, NULL, 0, 0, 0, "broken utf-8, start from 10 higher bits");
1826 test_utf82wchar((const char *)NULL, 0, NULL, 0, 0, 0, "invalid params, all 0");
1827 test_utf82wchar(u1, 0, NULL, 0, 0, 0, "invalid params, src buf not NULL");
1828 test_utf82wchar((const char *)NULL, 10, NULL, 0, 0, 0, "invalid params, src length is not 0");
1829
1830 // UCS-4 -> UTF-8 string.
1831 const char *const nullc = NULL;
1832 test_wchar2utf8(wbom, sizeof(wbom) / sizeof(*wbom), ubom, sizeof(ubom), UTF8_SKIP_BOM,
1833 sizeof(ubom), "BOM");
1834 test_wchar2utf8(wb2, sizeof(wb2) / sizeof(*wb2), nullc, 0, 0, 0, "prohibited wchars");
1835 test_wchar2utf8(wb2, sizeof(wb2) / sizeof(*wb2), nullc, 0, UTF8_IGNORE_ERROR, 2,
1836 "ignore prohibited wchars");
1837 test_wchar2utf8(w1, sizeof(w1) / sizeof(*w1), u1, sizeof(u1), 0, sizeof(u1),
1838 "w1/u1 1 octet chars");
1839 test_wchar2utf8(w2, sizeof(w2) / sizeof(*w2), u2, sizeof(u2), 0, sizeof(u2),
1840 "w2/u2 2 octets chars");
1841 test_wchar2utf8(w3, sizeof(w3) / sizeof(*w3), u3, sizeof(u3), 0, sizeof(u3),
1842 "w3/u3 3 octets chars");
1843 test_wchar2utf8(NULL, 0, nullc, 0, 0, 0, "invalid params, all 0");
1844 test_wchar2utf8(w1, 0, nullc, 0, 0, 0, "invalid params, src buf not NULL");
1845 test_wchar2utf8(w1, sizeof(w1) / sizeof(*w1), u1, 0, 0, 0, "invalid params, dst is not NULL");
1846 test_wchar2utf8(NULL, 10, nullc, 0, 0, 0, "invalid params, src length is not 0");
1847
1848 test_wchar2utf8(wm, sizeof(wm) / sizeof(*wm), um, sizeof(um), 0, sizeof(um),
1849 "wm/um mixed languages");
1850 test_wchar2utf8(wm, sizeof(wm) / sizeof(*wm), um, sizeof(um) - 1, 0, 0, "wm/um boundaries -1");
1851 test_wchar2utf8(wm, sizeof(wm) / sizeof(*wm), um, sizeof(um) + 1, 0, sizeof(um),
1852 "wm/um boundaries +1");
1853 test_wchar2utf8(wm, sizeof(wm) / sizeof(*wm), nullc, 0, 0, sizeof(um),
1854 "wm/um calculate length");
1855 test_utf82wchar(um, sizeof(um), wm, sizeof(wm) / sizeof(*wm), 0, sizeof(wm) / sizeof(*wm),
1856 "um/wm mixed languages");
1857 test_utf82wchar(um, sizeof(um), wm, sizeof(wm) / sizeof(*wm) + 1, 0, sizeof(wm) / sizeof(*wm),
1858 "um/wm boundaries +1");
1859 test_utf82wchar(um, sizeof(um), NULL, 0, 0, sizeof(wm) / sizeof(*wm), "um/wm calculate length");
1860
1861 // The following tests won't pass on systems (e.g., Cygwin) where sizeof wchar_t is 2. That's
1862 // due to several reasons but the primary one is that narrowing conversions of literals assigned
1863 // to the wchar_t arrays above don't result in values that will be treated as errors by the
1864 // conversion functions.
1865 #if WCHAR_MAX != 0xffff
1866 test_utf82wchar(u4, sizeof(u4), w4, sizeof(w4) / sizeof(*w4), 0, sizeof(w4) / sizeof(*w4),
1867 "u4/w4 4 octets chars");
1868 test_wchar2utf8(w4, sizeof(w4) / sizeof(*w4), u4, sizeof(u4), 0, sizeof(u4),
1869 "w4/u4 4 octets chars");
1870 test_wchar2utf8(wb, sizeof(wb) / sizeof(*wb), ub, sizeof(ub), 0, 0, "wb/ub bad chars");
1871 test_wchar2utf8(wb, sizeof(wb) / sizeof(*wb), ub, sizeof(ub), UTF8_IGNORE_ERROR, sizeof(ub),
1872 "wb/ub ignore bad chars");
1873 test_wchar2utf8(wb, sizeof(wb) / sizeof(*wb), nullc, 0, 0, 0,
1874 "wb calculate length of bad chars");
1875 test_wchar2utf8(wb, sizeof(wb) / sizeof(*wb), nullc, 0, UTF8_IGNORE_ERROR, sizeof(ub),
1876 "calculate length, ignore bad chars");
1877 test_utf82wchar(ub1, sizeof(ub1), wb1, sizeof(wb1) / sizeof(*wb1), UTF8_IGNORE_ERROR,
1878 sizeof(wb1) / sizeof(*wb1), "ub1/wb1 ignore bad chars");
1879 test_utf82wchar(ub1, sizeof(ub1), NULL, 0, 0, 0, "ub1 calculate length of bad chars");
1880 test_utf82wchar(ub1, sizeof(ub1), NULL, 0, UTF8_IGNORE_ERROR, sizeof(wb1) / sizeof(*wb1),
1881 "ub1 calculate length, ignore bad chars");
1882 #endif
1883 }
1884
test_feature_flags()1885 static void test_feature_flags() {
1886 say(L"Testing future feature flags");
1887 using ft = features_t;
1888 ft f;
1889 do_test(f.test(ft::stderr_nocaret));
1890 f.set(ft::stderr_nocaret, true);
1891 do_test(f.test(ft::stderr_nocaret));
1892 f.set(ft::stderr_nocaret, false);
1893 do_test(!f.test(ft::stderr_nocaret));
1894
1895 f.set_from_string(L"stderr-nocaret,nonsense");
1896 do_test(f.test(ft::stderr_nocaret));
1897 f.set_from_string(L"stderr-nocaret,no-stderr-nocaret,nonsense");
1898 do_test(!f.test(ft::stderr_nocaret));
1899
1900 // Ensure every metadata is represented once.
1901 size_t counts[ft::flag_count] = {};
1902 for (const auto &md : ft::metadata) {
1903 counts[md.flag]++;
1904 }
1905 for (size_t c : counts) {
1906 do_test(c == 1);
1907 }
1908 do_test(ft::metadata[ft::stderr_nocaret].name == wcstring(L"stderr-nocaret"));
1909 do_test(ft::metadata_for(L"stderr-nocaret") == &ft::metadata[ft::stderr_nocaret]);
1910 do_test(ft::metadata_for(L"not-a-flag") == nullptr);
1911 }
1912
test_escape_sequences()1913 static void test_escape_sequences() {
1914 say(L"Testing escape_sequences");
1915 layout_cache_t lc;
1916 if (lc.escape_code_length(L"") != 0)
1917 err(L"test_escape_sequences failed on line %d\n", __LINE__);
1918 if (lc.escape_code_length(L"abcd") != 0)
1919 err(L"test_escape_sequences failed on line %d\n", __LINE__);
1920 if (lc.escape_code_length(L"\x1B[2J") != 4)
1921 err(L"test_escape_sequences failed on line %d\n", __LINE__);
1922 if (lc.escape_code_length(L"\x1B[38;5;123mABC") != strlen("\x1B[38;5;123m"))
1923 err(L"test_escape_sequences failed on line %d\n", __LINE__);
1924 if (lc.escape_code_length(L"\x1B@") != 2)
1925 err(L"test_escape_sequences failed on line %d\n", __LINE__);
1926
1927 // iTerm2 escape sequences.
1928 if (lc.escape_code_length(L"\x1B]50;CurrentDir=test/foo\x07NOT_PART_OF_SEQUENCE") != 25)
1929 err(L"test_escape_sequences failed on line %d\n", __LINE__);
1930 if (lc.escape_code_length(L"\x1B]50;SetMark\x07NOT_PART_OF_SEQUENCE") != 13)
1931 err(L"test_escape_sequences failed on line %d\n", __LINE__);
1932 if (lc.escape_code_length(L"\x1B]6;1;bg;red;brightness;255\x07NOT_PART_OF_SEQUENCE") != 28)
1933 err(L"test_escape_sequences failed on line %d\n", __LINE__);
1934 if (lc.escape_code_length(L"\x1B]Pg4040ff\x1B\\NOT_PART_OF_SEQUENCE") != 12)
1935 err(L"test_escape_sequences failed on line %d\n", __LINE__);
1936 if (lc.escape_code_length(L"\x1B]blahblahblah\x1B\\") != 16)
1937 err(L"test_escape_sequences failed on line %d\n", __LINE__);
1938 if (lc.escape_code_length(L"\x1B]blahblahblah\x07") != 15)
1939 err(L"test_escape_sequences failed on line %d\n", __LINE__);
1940 }
1941
1942 class test_lru_t : public lru_cache_t<test_lru_t, int> {
1943 public:
1944 static constexpr size_t test_capacity = 16;
1945 using value_type = std::pair<wcstring, int>;
1946
test_lru_t()1947 test_lru_t() : lru_cache_t<test_lru_t, int>(test_capacity) {}
1948
1949 std::vector<value_type> evicted;
1950
entry_was_evicted(const wcstring & key,int val)1951 void entry_was_evicted(const wcstring &key, int val) { evicted.push_back({key, val}); }
1952
values() const1953 std::vector<value_type> values() const {
1954 std::vector<value_type> result;
1955 for (auto p : *this) {
1956 result.push_back(p);
1957 }
1958 return result;
1959 }
1960
ints() const1961 std::vector<int> ints() const {
1962 std::vector<int> result;
1963 for (auto p : *this) {
1964 result.push_back(p.second);
1965 }
1966 return result;
1967 }
1968 };
1969
test_lru()1970 static void test_lru() {
1971 say(L"Testing LRU cache");
1972
1973 test_lru_t cache;
1974 std::vector<std::pair<wcstring, int>> expected_evicted;
1975 std::vector<std::pair<wcstring, int>> expected_values;
1976 int total_nodes = 20;
1977 for (int i = 0; i < total_nodes; i++) {
1978 do_test(cache.size() == size_t(std::min(i, 16)));
1979 do_test(cache.values() == expected_values);
1980 if (i < 4) expected_evicted.push_back({to_string(i), i});
1981 // Adding the node the first time should work, and subsequent times should fail.
1982 do_test(cache.insert(to_string(i), i));
1983 do_test(!cache.insert(to_string(i), i + 1));
1984
1985 expected_values.push_back({to_string(i), i});
1986 while (expected_values.size() > test_lru_t::test_capacity) {
1987 expected_values.erase(expected_values.begin());
1988 }
1989 cache.check_sanity();
1990 }
1991 do_test(cache.evicted == expected_evicted);
1992 do_test(cache.values() == expected_values);
1993 cache.check_sanity();
1994
1995 // Stable-sort ints in reverse order
1996 // This a/2 check ensures that some different ints compare the same
1997 // It also gives us a different order than we started with
1998 auto comparer = [](int a, int b) { return a / 2 > b / 2; };
1999 std::vector<int> ints = cache.ints();
2000 std::stable_sort(ints.begin(), ints.end(), comparer);
2001
2002 cache.stable_sort(comparer);
2003 std::vector<int> new_ints = cache.ints();
2004 if (new_ints != ints) {
2005 auto commajoin = [](const std::vector<int> &vs) {
2006 wcstring ret;
2007 for (int v : vs) {
2008 append_format(ret, L"%d,", v);
2009 }
2010 if (!ret.empty()) ret.pop_back();
2011 return ret;
2012 };
2013 err(L"LRU stable sort failed. Expected %ls, got %ls\n", commajoin(new_ints).c_str(),
2014 commajoin(ints).c_str());
2015 }
2016
2017 cache.evict_all_nodes();
2018 do_test(cache.evicted.size() == size_t(total_nodes));
2019 }
2020
2021 /// An environment built around an std::map.
2022 struct test_environment_t : public environment_t {
2023 std::map<wcstring, wcstring> vars;
2024
gettest_environment_t2025 virtual maybe_t<env_var_t> get(const wcstring &key,
2026 env_mode_flags_t mode = ENV_DEFAULT) const override {
2027 UNUSED(mode);
2028 auto iter = vars.find(key);
2029 if (iter != vars.end()) {
2030 return env_var_t(iter->second, ENV_DEFAULT);
2031 }
2032 return none();
2033 }
2034
get_namestest_environment_t2035 wcstring_list_t get_names(int flags) const override {
2036 UNUSED(flags);
2037 wcstring_list_t result;
2038 for (const auto &kv : vars) {
2039 result.push_back(kv.first);
2040 }
2041 return result;
2042 }
2043 };
2044
2045 /// A test environment that knows about PWD.
2046 struct pwd_environment_t : public test_environment_t {
getpwd_environment_t2047 virtual maybe_t<env_var_t> get(const wcstring &key,
2048 env_mode_flags_t mode = ENV_DEFAULT) const override {
2049 if (key == L"PWD") {
2050 return env_var_t{wgetcwd(), 0};
2051 }
2052 return test_environment_t::get(key, mode);
2053 }
2054
get_namespwd_environment_t2055 wcstring_list_t get_names(int flags) const override {
2056 auto res = test_environment_t::get_names(flags);
2057 res.clear();
2058 if (std::count(res.begin(), res.end(), L"PWD") == 0) {
2059 res.push_back(L"PWD");
2060 }
2061 return res;
2062 }
2063 };
2064
2065 /// Perform parameter expansion and test if the output equals the zero-terminated parameter list
2066 /// supplied.
2067 ///
2068 /// \param in the string to expand
2069 /// \param flags the flags to send to expand_string
2070 /// \param ... A zero-terminated parameter list of values to test.
2071 /// After the zero terminator comes one more arg, a string, which is the error
2072 /// message to print if the test fails.
expand_test(const wchar_t * in,expand_flags_t flags,...)2073 static bool expand_test(const wchar_t *in, expand_flags_t flags, ...) {
2074 completion_list_t output;
2075 va_list va;
2076 bool res = true;
2077 wchar_t *arg;
2078 parse_error_list_t errors;
2079 pwd_environment_t pwd{};
2080 operation_context_t ctx{parser_t::principal_parser().shared(), pwd, no_cancel};
2081
2082 if (expand_string(in, &output, flags, ctx, &errors) == expand_result_t::error) {
2083 if (errors.empty()) {
2084 err(L"Bug: Parse error reported but no error text found.");
2085 } else {
2086 err(L"%ls", errors.at(0).describe(in, ctx.parser->is_interactive()).c_str());
2087 }
2088 return false;
2089 }
2090
2091 wcstring_list_t expected;
2092
2093 va_start(va, flags);
2094 while ((arg = va_arg(va, wchar_t *)) != NULL) {
2095 expected.push_back(wcstring(arg));
2096 }
2097 va_end(va);
2098
2099 std::set<wcstring> remaining(expected.begin(), expected.end());
2100 completion_list_t::const_iterator out_it = output.begin(), out_end = output.end();
2101 for (; out_it != out_end; ++out_it) {
2102 if (!remaining.erase(out_it->completion)) {
2103 res = false;
2104 break;
2105 }
2106 }
2107 if (!remaining.empty()) {
2108 res = false;
2109 }
2110
2111 if (!res) {
2112 arg = va_arg(va, wchar_t *);
2113 if (arg) {
2114 wcstring msg = L"Expected [";
2115 bool first = true;
2116 for (const wcstring &exp : expected) {
2117 if (!first) msg += L", ";
2118 first = false;
2119 msg += '"';
2120 msg += exp;
2121 msg += '"';
2122 }
2123 msg += L"], found [";
2124 first = true;
2125 for (const auto &completion : output) {
2126 if (!first) msg += L", ";
2127 first = false;
2128 msg += '"';
2129 msg += completion.completion;
2130 msg += '"';
2131 }
2132 msg += L"]";
2133 err(L"%ls\n%ls", arg, msg.c_str());
2134 }
2135 }
2136
2137 va_end(va);
2138
2139 return res;
2140 }
2141
2142 /// Test globbing and other parameter expansion.
test_expand()2143 static void test_expand() {
2144 say(L"Testing parameter expansion");
2145 const expand_flags_t noflags{};
2146
2147 expand_test(L"foo", noflags, L"foo", 0, L"Strings do not expand to themselves");
2148 expand_test(L"a{b,c,d}e", noflags, L"abe", L"ace", L"ade", 0, L"Bracket expansion is broken");
2149 expand_test(L"a*", expand_flag::skip_wildcards, L"a*", 0, L"Cannot skip wildcard expansion");
2150 expand_test(L"/bin/l\\0", expand_flag::for_completions, 0,
2151 L"Failed to handle null escape in expansion");
2152 expand_test(L"foo\\$bar", expand_flag::skip_variables, L"foo$bar", 0,
2153 L"Failed to handle dollar sign in variable-skipping expansion");
2154
2155 // bb
2156 // x
2157 // bar
2158 // baz
2159 // xxx
2160 // yyy
2161 // bax
2162 // xxx
2163 // lol
2164 // nub
2165 // q
2166 // .foo
2167 // aaa
2168 // aaa2
2169 // x
2170 if (system("mkdir -p test/fish_expand_test/")) err(L"mkdir failed");
2171 if (system("mkdir -p test/fish_expand_test/bb/")) err(L"mkdir failed");
2172 if (system("mkdir -p test/fish_expand_test/baz/")) err(L"mkdir failed");
2173 if (system("mkdir -p test/fish_expand_test/bax/")) err(L"mkdir failed");
2174 if (system("mkdir -p test/fish_expand_test/lol/nub/")) err(L"mkdir failed");
2175 if (system("mkdir -p test/fish_expand_test/aaa/")) err(L"mkdir failed");
2176 if (system("mkdir -p test/fish_expand_test/aaa2/")) err(L"mkdir failed");
2177 if (system("touch test/fish_expand_test/.foo")) err(L"touch failed");
2178 if (system("touch test/fish_expand_test/bb/x")) err(L"touch failed");
2179 if (system("touch test/fish_expand_test/bar")) err(L"touch failed");
2180 if (system("touch test/fish_expand_test/bax/xxx")) err(L"touch failed");
2181 if (system("touch test/fish_expand_test/baz/xxx")) err(L"touch failed");
2182 if (system("touch test/fish_expand_test/baz/yyy")) err(L"touch failed");
2183 if (system("touch test/fish_expand_test/lol/nub/q")) err(L"touch failed");
2184 if (system("touch test/fish_expand_test/aaa2/x")) err(L"touch failed");
2185
2186 // This is checking that .* does NOT match . and ..
2187 // (https://github.com/fish-shell/fish-shell/issues/270). But it does have to match literal
2188 // components (e.g. "./*" has to match the same as "*".
2189 const wchar_t *const wnull = NULL;
2190 expand_test(L"test/fish_expand_test/.*", noflags, L"test/fish_expand_test/.foo", wnull,
2191 L"Expansion not correctly handling dotfiles");
2192
2193 expand_test(L"test/fish_expand_test/./.*", noflags, L"test/fish_expand_test/./.foo", wnull,
2194 L"Expansion not correctly handling literal path components in dotfiles");
2195
2196 expand_test(L"test/fish_expand_test/*/xxx", noflags, L"test/fish_expand_test/bax/xxx",
2197 L"test/fish_expand_test/baz/xxx", wnull, L"Glob did the wrong thing 1");
2198
2199 expand_test(L"test/fish_expand_test/*z/xxx", noflags, L"test/fish_expand_test/baz/xxx", wnull,
2200 L"Glob did the wrong thing 2");
2201
2202 expand_test(L"test/fish_expand_test/**z/xxx", noflags, L"test/fish_expand_test/baz/xxx", wnull,
2203 L"Glob did the wrong thing 3");
2204
2205 expand_test(L"test/fish_expand_test////baz/xxx", noflags, L"test/fish_expand_test////baz/xxx",
2206 wnull, L"Glob did the wrong thing 3");
2207
2208 expand_test(L"test/fish_expand_test/b**", noflags, L"test/fish_expand_test/bb",
2209 L"test/fish_expand_test/bb/x", L"test/fish_expand_test/bar",
2210 L"test/fish_expand_test/bax", L"test/fish_expand_test/bax/xxx",
2211 L"test/fish_expand_test/baz", L"test/fish_expand_test/baz/xxx",
2212 L"test/fish_expand_test/baz/yyy", wnull, L"Glob did the wrong thing 4");
2213
2214 // A trailing slash should only produce directories.
2215 expand_test(L"test/fish_expand_test/b*/", noflags, L"test/fish_expand_test/bb/",
2216 L"test/fish_expand_test/baz/", L"test/fish_expand_test/bax/", wnull,
2217 L"Glob did the wrong thing 5");
2218
2219 expand_test(L"test/fish_expand_test/b**/", noflags, L"test/fish_expand_test/bb/",
2220 L"test/fish_expand_test/baz/", L"test/fish_expand_test/bax/", wnull,
2221 L"Glob did the wrong thing 6");
2222
2223 expand_test(L"test/fish_expand_test/**/q", noflags, L"test/fish_expand_test/lol/nub/q", wnull,
2224 L"Glob did the wrong thing 7");
2225
2226 expand_test(L"test/fish_expand_test/BA", expand_flag::for_completions,
2227 L"test/fish_expand_test/bar", L"test/fish_expand_test/bax/",
2228 L"test/fish_expand_test/baz/", wnull, L"Case insensitive test did the wrong thing");
2229
2230 expand_test(L"test/fish_expand_test/BA", expand_flag::for_completions,
2231 L"test/fish_expand_test/bar", L"test/fish_expand_test/bax/",
2232 L"test/fish_expand_test/baz/", wnull, L"Case insensitive test did the wrong thing");
2233
2234 expand_test(L"test/fish_expand_test/bb/yyy", expand_flag::for_completions,
2235 /* nothing! */ wnull, L"Wrong fuzzy matching 1");
2236
2237 expand_test(L"test/fish_expand_test/bb/x",
2238 expand_flags_t{expand_flag::for_completions, expand_flag::fuzzy_match}, L"",
2239 wnull, // we just expect the empty string since this is an exact match
2240 L"Wrong fuzzy matching 2");
2241
2242 // Some vswprintfs refuse to append ANY_STRING in a format specifiers, so don't use
2243 // format_string here.
2244 const expand_flags_t fuzzy_comp{expand_flag::for_completions, expand_flag::fuzzy_match};
2245 const wcstring any_str_str(1, ANY_STRING);
2246 expand_test(L"test/fish_expand_test/b/xx*", fuzzy_comp,
2247 (L"test/fish_expand_test/bax/xx" + any_str_str).c_str(),
2248 (L"test/fish_expand_test/baz/xx" + any_str_str).c_str(), wnull,
2249 L"Wrong fuzzy matching 3");
2250
2251 expand_test(L"test/fish_expand_test/b/yyy", fuzzy_comp, L"test/fish_expand_test/baz/yyy", wnull,
2252 L"Wrong fuzzy matching 4");
2253
2254 expand_test(L"test/fish_expand_test/aa/x", fuzzy_comp, L"test/fish_expand_test/aaa2/x", wnull,
2255 L"Wrong fuzzy matching 5");
2256
2257 expand_test(L"test/fish_expand_test/aaa/x", fuzzy_comp, wnull,
2258 L"Wrong fuzzy matching 6 - shouldn't remove valid directory names (#3211)");
2259
2260 if (!expand_test(L"test/fish_expand_test/.*", noflags, L"test/fish_expand_test/.foo", 0)) {
2261 err(L"Expansion not correctly handling dotfiles");
2262 }
2263 if (!expand_test(L"test/fish_expand_test/./.*", noflags, L"test/fish_expand_test/./.foo", 0)) {
2264 err(L"Expansion not correctly handling literal path components in dotfiles");
2265 }
2266
2267 if (!pushd("test/fish_expand_test")) return;
2268
2269 expand_test(L"b/xx", fuzzy_comp, L"bax/xxx", L"baz/xxx", wnull, L"Wrong fuzzy matching 5");
2270
2271 // multiple slashes with fuzzy matching - #3185
2272 expand_test(L"l///n", fuzzy_comp, L"lol///nub/", wnull, L"Wrong fuzzy matching 6");
2273
2274 popd();
2275 }
2276
test_expand_overflow()2277 static void test_expand_overflow() {
2278 say(L"Testing overflowing expansions");
2279 // Ensure that we have sane limits on number of expansions - see #7497.
2280
2281 // Make a list of 64 elements, then expand it cartesian-style 64 times.
2282 // This is far too large to expand.
2283 wcstring_list_t vals;
2284 wcstring expansion;
2285 for (int i = 1; i <= 64; i++) {
2286 vals.push_back(to_string(i));
2287 expansion.append(L"$bigvar");
2288 }
2289
2290 auto parser = parser_t::principal_parser().shared();
2291 parser->vars().push(true);
2292 int set = parser->vars().set(L"bigvar", ENV_LOCAL, std::move(vals));
2293 do_test(set == ENV_OK);
2294
2295 parse_error_list_t errors;
2296 operation_context_t ctx{parser, parser->vars(), no_cancel};
2297
2298 // We accept only 1024 completions.
2299 completion_receiver_t output{1024};
2300
2301 auto res = expand_string(expansion, &output, expand_flags_t{}, ctx, &errors);
2302 do_test(!errors.empty());
2303 do_test(res == expand_result_t::error);
2304
2305 parser->vars().pop();
2306 }
2307
test_fuzzy_match()2308 static void test_fuzzy_match() {
2309 say(L"Testing fuzzy string matching");
2310 // Check that a string fuzzy match has the expected type and case folding.
2311 using type_t = string_fuzzy_match_t::contain_type_t;
2312 using case_fold_t = string_fuzzy_match_t::case_fold_t;
2313 auto test_fuzzy = [](const wchar_t *inp, const wchar_t *exp, type_t type,
2314 case_fold_t fold) -> bool {
2315 auto m = string_fuzzy_match_string(inp, exp);
2316 return m && m->type == type && m->case_fold == fold;
2317 };
2318
2319 do_test(test_fuzzy(L"", L"", type_t::exact, case_fold_t::samecase));
2320 do_test(test_fuzzy(L"alpha", L"alpha", type_t::exact, case_fold_t::samecase));
2321 do_test(test_fuzzy(L"alp", L"alpha", type_t::prefix, case_fold_t::samecase));
2322 do_test(test_fuzzy(L"alpha", L"AlPhA", type_t::exact, case_fold_t::smartcase));
2323 do_test(test_fuzzy(L"alpha", L"AlPhA!", type_t::prefix, case_fold_t::smartcase));
2324 do_test(test_fuzzy(L"ALPHA", L"alpha!", type_t::prefix, case_fold_t::icase));
2325 do_test(test_fuzzy(L"ALPHA!", L"alPhA!", type_t::exact, case_fold_t::icase));
2326 do_test(test_fuzzy(L"alPh", L"ALPHA!", type_t::prefix, case_fold_t::icase));
2327 do_test(test_fuzzy(L"LPH", L"ALPHA!", type_t::substr, case_fold_t::samecase));
2328 do_test(test_fuzzy(L"lph", L"AlPhA!", type_t::substr, case_fold_t::smartcase));
2329 do_test(test_fuzzy(L"lPh", L"ALPHA!", type_t::substr, case_fold_t::icase));
2330 do_test(test_fuzzy(L"AA", L"ALPHA!", type_t::subseq, case_fold_t::samecase));
2331 do_test(!string_fuzzy_match_string(L"lh", L"ALPHA!").has_value()); // no subseq icase
2332 do_test(!string_fuzzy_match_string(L"BB", L"ALPHA!").has_value());
2333 }
2334
test_ifind()2335 static void test_ifind() {
2336 say(L"Testing ifind");
2337 do_test(ifind(std::string{"alpha"}, std::string{"alpha"}) == 0);
2338 do_test(ifind(wcstring{L"alphab"}, wcstring{L"alpha"}) == 0);
2339 do_test(ifind(std::string{"alpha"}, std::string{"balpha"}) == std::string::npos);
2340 do_test(ifind(std::string{"balpha"}, std::string{"alpha"}) == 1);
2341 do_test(ifind(std::string{"alphab"}, std::string{"balpha"}) == std::string::npos);
2342 do_test(ifind(std::string{"balpha"}, std::string{"lPh"}) == 2);
2343 do_test(ifind(std::string{"balpha"}, std::string{"Plh"}) == std::string::npos);
2344 }
2345
test_ifind_fuzzy()2346 static void test_ifind_fuzzy() {
2347 say(L"Testing ifind with fuzzy logic");
2348 do_test(ifind(std::string{"alpha"}, std::string{"alpha"}, true) == 0);
2349 do_test(ifind(wcstring{L"alphab"}, wcstring{L"alpha"}, true) == 0);
2350 do_test(ifind(std::string{"alpha-b"}, std::string{"alpha_b"}, true) == 0);
2351 do_test(ifind(std::string{"alpha-_"}, std::string{"alpha_-"}, true) == 0);
2352 do_test(ifind(std::string{"alpha-b"}, std::string{"alpha b"}, true) == std::string::npos);
2353 }
2354
test_abbreviations()2355 static void test_abbreviations() {
2356 say(L"Testing abbreviations");
2357 auto &vars = parser_t::principal_parser().vars();
2358 vars.push(true);
2359
2360 const std::vector<std::pair<const wcstring, const wcstring>> abbreviations = {
2361 {L"gc", L"git checkout"},
2362 {L"foo", L"bar"},
2363 {L"gx", L"git checkout"},
2364 };
2365 for (const auto &kv : abbreviations) {
2366 int ret = vars.set_one(L"_fish_abbr_" + kv.first, ENV_LOCAL, kv.second);
2367 if (ret != 0) err(L"Unable to set abbreviation variable");
2368 }
2369
2370 if (expand_abbreviation(L"", vars)) err(L"Unexpected success with empty abbreviation");
2371 if (expand_abbreviation(L"nothing", vars)) err(L"Unexpected success with missing abbreviation");
2372
2373 auto mresult = expand_abbreviation(L"gc", vars);
2374 if (!mresult) err(L"Unexpected failure with gc abbreviation");
2375 if (*mresult != L"git checkout") err(L"Wrong abbreviation result for gc");
2376
2377 mresult = expand_abbreviation(L"foo", vars);
2378 if (!mresult) err(L"Unexpected failure with foo abbreviation");
2379 if (*mresult != L"bar") err(L"Wrong abbreviation result for foo");
2380
2381 maybe_t<wcstring> result;
2382 auto expand_abbreviation_in_command = [](const wcstring &cmdline, size_t cursor_pos,
2383 const environment_t &vars) -> maybe_t<wcstring> {
2384 if (auto edit = reader_expand_abbreviation_in_command(cmdline, cursor_pos, vars)) {
2385 wcstring cmdline_expanded = cmdline;
2386 apply_edit(&cmdline_expanded, *edit);
2387 return cmdline_expanded;
2388 }
2389 return none_t();
2390 };
2391 result = expand_abbreviation_in_command(L"just a command", 3, vars);
2392 if (result) err(L"Command wrongly expanded on line %ld", (long)__LINE__);
2393 result = expand_abbreviation_in_command(L"gc somebranch", 0, vars);
2394 if (!result) err(L"Command not expanded on line %ld", (long)__LINE__);
2395
2396 result = expand_abbreviation_in_command(L"gc somebranch", const_strlen(L"gc"), vars);
2397 if (!result) err(L"gc not expanded");
2398 if (result != L"git checkout somebranch")
2399 err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
2400
2401 // Space separation.
2402 result = expand_abbreviation_in_command(L"gx somebranch", const_strlen(L"gc"), vars);
2403 if (!result) err(L"gx not expanded");
2404 if (result != L"git checkout somebranch")
2405 err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
2406
2407 result = expand_abbreviation_in_command(L"echo hi ; gc somebranch",
2408 const_strlen(L"echo hi ; g"), vars);
2409 if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
2410 if (result != L"echo hi ; git checkout somebranch")
2411 err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
2412
2413 result = expand_abbreviation_in_command(L"echo (echo (echo (echo (gc ",
2414 const_strlen(L"echo (echo (echo (echo (gc"), vars);
2415 if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
2416 if (result != L"echo (echo (echo (echo (git checkout ")
2417 err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
2418
2419 // If commands should be expanded.
2420 result = expand_abbreviation_in_command(L"if gc", const_strlen(L"if gc"), vars);
2421 if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
2422 if (result != L"if git checkout")
2423 err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
2424
2425 // Others should not be.
2426 result = expand_abbreviation_in_command(L"of gc", const_strlen(L"of gc"), vars);
2427 if (result) err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
2428
2429 // Others should not be.
2430 result = expand_abbreviation_in_command(L"command gc", const_strlen(L"command gc"), vars);
2431 if (result) err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
2432
2433 vars.pop();
2434 }
2435
2436 /// Test path functions.
test_path()2437 static void test_path() {
2438 say(L"Testing path functions");
2439
2440 wcstring path = L"//foo//////bar/";
2441 path_make_canonical(path);
2442 if (path != L"/foo/bar") {
2443 err(L"Bug in canonical PATH code");
2444 }
2445
2446 path = L"/";
2447 path_make_canonical(path);
2448 if (path != L"/") {
2449 err(L"Bug in canonical PATH code");
2450 }
2451
2452 if (paths_are_equivalent(L"/foo/bar/baz", L"foo/bar/baz"))
2453 err(L"Bug in canonical PATH code on line %ld", (long)__LINE__);
2454 if (!paths_are_equivalent(L"///foo///bar/baz", L"/foo/bar////baz//"))
2455 err(L"Bug in canonical PATH code on line %ld", (long)__LINE__);
2456 if (!paths_are_equivalent(L"/foo/bar/baz", L"/foo/bar/baz"))
2457 err(L"Bug in canonical PATH code on line %ld", (long)__LINE__);
2458 if (!paths_are_equivalent(L"/", L"/"))
2459 err(L"Bug in canonical PATH code on line %ld", (long)__LINE__);
2460
2461 do_test(path_apply_working_directory(L"abc", L"/def/") == L"/def/abc");
2462 do_test(path_apply_working_directory(L"abc/", L"/def/") == L"/def/abc/");
2463 do_test(path_apply_working_directory(L"/abc/", L"/def/") == L"/abc/");
2464 do_test(path_apply_working_directory(L"/abc", L"/def/") == L"/abc");
2465 do_test(path_apply_working_directory(L"", L"/def/") == L"");
2466 do_test(path_apply_working_directory(L"abc", L"") == L"abc");
2467 }
2468
test_pager_navigation()2469 static void test_pager_navigation() {
2470 say(L"Testing pager navigation");
2471
2472 // Generate 19 strings of width 10. There's 2 spaces between completions, and our term size is
2473 // 80; these can therefore fit into 6 columns (6 * 12 - 2 = 70) or 5 columns (58) but not 7
2474 // columns (7 * 12 - 2 = 82).
2475 //
2476 // You can simulate this test by creating 19 files named "file00.txt" through "file_18.txt".
2477 completion_list_t completions;
2478 for (size_t i = 0; i < 19; i++) {
2479 append_completion(&completions, L"abcdefghij");
2480 }
2481
2482 pager_t pager;
2483 pager.set_completions(completions);
2484 pager.set_term_size(termsize_t::defaults());
2485 page_rendering_t render = pager.render();
2486
2487 if (render.term_width != 80) err(L"Wrong term width");
2488 if (render.term_height != 24) err(L"Wrong term height");
2489
2490 size_t rows = 4, cols = 5;
2491
2492 // We have 19 completions. We can fit into 6 columns with 4 rows or 5 columns with 4 rows; the
2493 // second one is better and so is what we ought to have picked.
2494 if (render.rows != rows) err(L"Wrong row count");
2495 if (render.cols != cols) err(L"Wrong column count");
2496
2497 // Initially expect to have no completion index.
2498 if (render.selected_completion_idx != (size_t)(-1)) {
2499 err(L"Wrong initial selection");
2500 }
2501
2502 // Here are navigation directions and where we expect the selection to be.
2503 const struct {
2504 selection_motion_t dir;
2505 size_t sel;
2506 } cmds[] = {
2507 // Tab completion to get into the list.
2508 {selection_motion_t::next, 0},
2509
2510 // Westward motion in upper left goes to the last filled column in the last row.
2511 {selection_motion_t::west, 15},
2512 // East goes back.
2513 {selection_motion_t::east, 0},
2514
2515 // "Next" motion goes down the column.
2516 {selection_motion_t::next, 1},
2517 {selection_motion_t::next, 2},
2518
2519 {selection_motion_t::west, 17},
2520 {selection_motion_t::east, 2},
2521 {selection_motion_t::east, 6},
2522 {selection_motion_t::east, 10},
2523 {selection_motion_t::east, 14},
2524 {selection_motion_t::east, 18},
2525
2526 {selection_motion_t::west, 14},
2527 {selection_motion_t::east, 18},
2528
2529 // Eastward motion wraps back to the upper left, westward goes to the prior column.
2530 {selection_motion_t::east, 3},
2531 {selection_motion_t::east, 7},
2532 {selection_motion_t::east, 11},
2533 {selection_motion_t::east, 15},
2534
2535 // Pages.
2536 {selection_motion_t::page_north, 12},
2537 {selection_motion_t::page_south, 15},
2538 {selection_motion_t::page_north, 12},
2539 {selection_motion_t::east, 16},
2540 {selection_motion_t::page_south, 18},
2541 {selection_motion_t::east, 3},
2542 {selection_motion_t::north, 2},
2543 {selection_motion_t::page_north, 0},
2544 {selection_motion_t::page_south, 3},
2545
2546 };
2547 for (size_t i = 0; i < sizeof cmds / sizeof *cmds; i++) {
2548 pager.select_next_completion_in_direction(cmds[i].dir, render);
2549 pager.update_rendering(&render);
2550 if (cmds[i].sel != render.selected_completion_idx) {
2551 err(L"For command %lu, expected selection %lu, but found instead %lu\n", i, cmds[i].sel,
2552 render.selected_completion_idx);
2553 }
2554 }
2555 }
2556
2557 struct pager_layout_testcase_t {
2558 int width;
2559 const wchar_t *expected;
2560
2561 // Run ourselves as a test case.
2562 // Set our data on the pager, and then check the rendering.
2563 // We should have one line, and it should have our expected text.
runpager_layout_testcase_t2564 void run(pager_t &pager) const {
2565 pager.set_term_size(termsize_t{this->width, 24});
2566 page_rendering_t rendering = pager.render();
2567 const screen_data_t &sd = rendering.screen_data;
2568 do_test(sd.line_count() == 1);
2569 if (sd.line_count() > 0) {
2570 wcstring expected = this->expected;
2571
2572 // hack: handle the case where ellipsis is not L'\x2026'
2573 wchar_t ellipsis_char = get_ellipsis_char();
2574 if (ellipsis_char != L'\x2026') {
2575 std::replace(expected.begin(), expected.end(), L'\x2026', ellipsis_char);
2576 }
2577
2578 wcstring text;
2579 for (const auto &p : sd.line(0).text) {
2580 text.push_back(p.character);
2581 }
2582 if (text != expected) {
2583 std::fwprintf(stderr, L"width %d got %zu<%ls>, expected %zu<%ls>\n", this->width,
2584 text.length(), text.c_str(), expected.length(), expected.c_str());
2585 for (size_t i = 0; i < std::max(text.length(), expected.length()); i++) {
2586 std::fwprintf(stderr, L"i %zu got <%lx> expected <%lx>\n", i,
2587 i >= text.length() ? 0xffff : text[i],
2588 i >= expected.length() ? 0xffff : expected[i]);
2589 }
2590 }
2591 do_test(text == expected);
2592 }
2593 }
2594 };
2595
test_pager_layout()2596 static void test_pager_layout() {
2597 // These tests are woefully incomplete
2598 // They only test the truncation logic for a single completion
2599 say(L"Testing pager layout");
2600 pager_t pager;
2601
2602 // These test cases have equal completions and descriptions
2603 const completion_t c1(L"abcdefghij", L"1234567890");
2604 pager.set_completions(completion_list_t(1, c1));
2605 const pager_layout_testcase_t testcases1[] = {
2606 {26, L"abcdefghij (1234567890)"}, {25, L"abcdefghij (1234567890)"},
2607 {24, L"abcdefghij (1234567890)"}, {23, L"abcdefghij (12345678…)"},
2608 {22, L"abcdefghij (1234567…)"}, {21, L"abcdefghij (123456…)"},
2609 {20, L"abcdefghij (12345…)"}, {19, L"abcdefghij (1234…)"},
2610 {18, L"abcdefgh… (1234…)"}, {17, L"abcdefg… (1234…)"},
2611 {16, L"abcdefg… (123…)"}, {0, NULL} // sentinel terminator
2612 };
2613 for (size_t i = 0; testcases1[i].expected != NULL; i++) {
2614 testcases1[i].run(pager);
2615 }
2616
2617 // These test cases have heavyweight completions
2618 const completion_t c2(L"abcdefghijklmnopqrs", L"1");
2619 pager.set_completions(completion_list_t(1, c2));
2620 const pager_layout_testcase_t testcases2[] = {
2621 {26, L"abcdefghijklmnopqrs (1)"}, {25, L"abcdefghijklmnopqrs (1)"},
2622 {24, L"abcdefghijklmnopqrs (1)"}, {23, L"abcdefghijklmnopq… (1)"},
2623 {22, L"abcdefghijklmnop… (1)"}, {21, L"abcdefghijklmno… (1)"},
2624 {20, L"abcdefghijklmn… (1)"}, {19, L"abcdefghijklm… (1)"},
2625 {18, L"abcdefghijkl… (1)"}, {17, L"abcdefghijk… (1)"},
2626 {16, L"abcdefghij… (1)"}, {0, NULL} // sentinel terminator
2627 };
2628 for (size_t i = 0; testcases2[i].expected != NULL; i++) {
2629 testcases2[i].run(pager);
2630 }
2631
2632 // These test cases have no descriptions
2633 const completion_t c3(L"abcdefghijklmnopqrst", L"");
2634 pager.set_completions(completion_list_t(1, c3));
2635 const pager_layout_testcase_t testcases3[] = {
2636 {26, L"abcdefghijklmnopqrst"}, {25, L"abcdefghijklmnopqrst"},
2637 {24, L"abcdefghijklmnopqrst"}, {23, L"abcdefghijklmnopqrst"},
2638 {22, L"abcdefghijklmnopqrst"}, {21, L"abcdefghijklmnopqrst"},
2639 {20, L"abcdefghijklmnopqrst"}, {19, L"abcdefghijklmnopqr…"},
2640 {18, L"abcdefghijklmnopq…"}, {17, L"abcdefghijklmnop…"},
2641 {16, L"abcdefghijklmno…"}, {0, NULL} // sentinel terminator
2642 };
2643 for (size_t i = 0; testcases3[i].expected != NULL; i++) {
2644 testcases3[i].run(pager);
2645 }
2646 }
2647
2648 enum word_motion_t { word_motion_left, word_motion_right };
test_1_word_motion(word_motion_t motion,move_word_style_t style,const wcstring & test)2649 static void test_1_word_motion(word_motion_t motion, move_word_style_t style,
2650 const wcstring &test) {
2651 wcstring command;
2652 std::set<size_t> stops;
2653
2654 // Carets represent stops and should be cut out of the command.
2655 for (wchar_t wc : test) {
2656 if (wc == L'^') {
2657 stops.insert(command.size());
2658 } else {
2659 command.push_back(wc);
2660 }
2661 }
2662
2663 size_t idx, end;
2664 if (motion == word_motion_left) {
2665 idx = *std::max_element(stops.begin(), stops.end());
2666 end = 0;
2667 } else {
2668 idx = *std::min_element(stops.begin(), stops.end());
2669 end = command.size();
2670 }
2671 stops.erase(idx);
2672
2673 move_word_state_machine_t sm(style);
2674 while (idx != end) {
2675 size_t char_idx = (motion == word_motion_left ? idx - 1 : idx);
2676 wchar_t wc = command.at(char_idx);
2677 bool will_stop = !sm.consume_char(wc);
2678 // std::fwprintf(stdout, L"idx %lu, looking at %lu (%c): %d\n", idx, char_idx, (char)wc,
2679 // will_stop);
2680 bool expected_stop = (stops.count(idx) > 0);
2681 if (will_stop != expected_stop) {
2682 wcstring tmp = command;
2683 tmp.insert(idx, L"^");
2684 const char *dir = (motion == word_motion_left ? "left" : "right");
2685 if (will_stop) {
2686 err(L"Word motion: moving %s, unexpected stop at idx %lu: '%ls'", dir, idx,
2687 tmp.c_str());
2688 } else if (!will_stop && expected_stop) {
2689 err(L"Word motion: moving %s, should have stopped at idx %lu: '%ls'", dir, idx,
2690 tmp.c_str());
2691 }
2692 }
2693 // We don't expect to stop here next time.
2694 if (expected_stop) {
2695 stops.erase(idx);
2696 }
2697 if (will_stop) {
2698 sm.reset();
2699 } else {
2700 idx += (motion == word_motion_left ? -1 : 1);
2701 }
2702 }
2703 }
2704
2705 /// Test word motion (forward-word, etc.). Carets represent cursor stops.
test_word_motion()2706 static void test_word_motion() {
2707 say(L"Testing word motion");
2708 test_1_word_motion(word_motion_left, move_word_style_punctuation, L"^echo ^hello_^world.^txt^");
2709 test_1_word_motion(word_motion_right, move_word_style_punctuation,
2710 L"^echo^ hello^_world^.txt^");
2711
2712 test_1_word_motion(word_motion_left, move_word_style_punctuation,
2713 L"echo ^foo_^foo_^foo/^/^/^/^/^ ^");
2714 test_1_word_motion(word_motion_right, move_word_style_punctuation,
2715 L"^echo^ foo^_foo^_foo^/^/^/^/^/ ^");
2716
2717 test_1_word_motion(word_motion_left, move_word_style_path_components, L"^/^foo/^bar/^baz/^");
2718 test_1_word_motion(word_motion_left, move_word_style_path_components, L"^echo ^--foo ^--bar^");
2719 test_1_word_motion(word_motion_left, move_word_style_path_components,
2720 L"^echo ^hi ^> ^/^dev/^null^");
2721
2722 test_1_word_motion(word_motion_left, move_word_style_path_components,
2723 L"^echo ^/^foo/^bar{^aaa,^bbb,^ccc}^bak/^");
2724 test_1_word_motion(word_motion_left, move_word_style_path_components, L"^echo ^bak ^///^");
2725 test_1_word_motion(word_motion_left, move_word_style_path_components, L"^aaa ^@ ^@^aaa^");
2726 test_1_word_motion(word_motion_left, move_word_style_path_components, L"^aaa ^a ^@^aaa^");
2727 test_1_word_motion(word_motion_left, move_word_style_path_components, L"^aaa ^@@@ ^@@^aa^");
2728 test_1_word_motion(word_motion_left, move_word_style_path_components, L"^aa^@@ ^aa@@^a^");
2729
2730 test_1_word_motion(word_motion_right, move_word_style_punctuation, L"^a^ bcd^");
2731 test_1_word_motion(word_motion_right, move_word_style_punctuation, L"a^b^ cde^");
2732 test_1_word_motion(word_motion_right, move_word_style_punctuation, L"^ab^ cde^");
2733
2734 test_1_word_motion(word_motion_right, move_word_style_whitespace, L"^^a-b-c^ d-e-f");
2735 test_1_word_motion(word_motion_right, move_word_style_whitespace, L"^a-b-c^\n d-e-f^ ");
2736 test_1_word_motion(word_motion_right, move_word_style_whitespace, L"^a-b-c^\n\nd-e-f^ ");
2737 }
2738
2739 /// Test is_potential_path.
test_is_potential_path()2740 static void test_is_potential_path() {
2741 say(L"Testing is_potential_path");
2742
2743 // Directories
2744 if (system("mkdir -p test/is_potential_path_test/alpha/")) err(L"mkdir failed");
2745 if (system("mkdir -p test/is_potential_path_test/beta/")) err(L"mkdir failed");
2746
2747 // Files
2748 if (system("touch test/is_potential_path_test/aardvark")) err(L"touch failed");
2749 if (system("touch test/is_potential_path_test/gamma")) err(L"touch failed");
2750
2751 const wcstring wd = L"test/is_potential_path_test/";
2752 const wcstring_list_t wds({L".", wd});
2753
2754 operation_context_t ctx{env_stack_t::principal()};
2755 do_test(is_potential_path(L"al", wds, ctx, PATH_REQUIRE_DIR));
2756 do_test(is_potential_path(L"alpha/", wds, ctx, PATH_REQUIRE_DIR));
2757 do_test(is_potential_path(L"aard", wds, ctx, 0));
2758
2759 do_test(!is_potential_path(L"balpha/", wds, ctx, PATH_REQUIRE_DIR));
2760 do_test(!is_potential_path(L"aard", wds, ctx, PATH_REQUIRE_DIR));
2761 do_test(!is_potential_path(L"aarde", wds, ctx, PATH_REQUIRE_DIR));
2762 do_test(!is_potential_path(L"aarde", wds, ctx, 0));
2763
2764 do_test(is_potential_path(L"test/is_potential_path_test/aardvark", wds, ctx, 0));
2765 do_test(is_potential_path(L"test/is_potential_path_test/al", wds, ctx, PATH_REQUIRE_DIR));
2766 do_test(is_potential_path(L"test/is_potential_path_test/aardv", wds, ctx, 0));
2767
2768 do_test(
2769 !is_potential_path(L"test/is_potential_path_test/aardvark", wds, ctx, PATH_REQUIRE_DIR));
2770 do_test(!is_potential_path(L"test/is_potential_path_test/al/", wds, ctx, 0));
2771 do_test(!is_potential_path(L"test/is_potential_path_test/ar", wds, ctx, 0));
2772
2773 do_test(is_potential_path(L"/usr", wds, ctx, PATH_REQUIRE_DIR));
2774 }
2775
2776 /// Test the 'test' builtin.
2777 maybe_t<int> builtin_test(parser_t &parser, io_streams_t &streams, const wchar_t **argv);
run_one_test_test(int expected,const wcstring_list_t & lst,bool bracket)2778 static bool run_one_test_test(int expected, const wcstring_list_t &lst, bool bracket) {
2779 parser_t &parser = parser_t::principal_parser();
2780 wcstring_list_t argv;
2781 argv.push_back(bracket ? L"[" : L"test");
2782 argv.insert(argv.end(), lst.begin(), lst.end());
2783 if (bracket) argv.push_back(L"]");
2784
2785 null_terminated_array_t<wchar_t> cargv(argv);
2786
2787 null_output_stream_t null{};
2788 io_streams_t streams(null, null);
2789 maybe_t<int> result = builtin_test(parser, streams, cargv.get());
2790
2791 if (result != expected) {
2792 std::wstring got = result ? std::to_wstring(result.value()) : L"nothing";
2793 err(L"expected builtin_test() to return %d, got %s", expected, got.c_str());
2794 }
2795 return result == expected;
2796 }
2797
run_test_test(int expected,const wcstring & str)2798 static bool run_test_test(int expected, const wcstring &str) {
2799 // We need to tokenize the string in the same manner a normal shell would do. This is because we
2800 // need to test things like quoted strings that have leading and trailing whitespace.
2801 auto parser = parser_t::principal_parser().shared();
2802 null_environment_t nullenv{};
2803 operation_context_t ctx{parser, nullenv, no_cancel};
2804 completion_list_t comps = parser_t::expand_argument_list(str, expand_flags_t{}, ctx);
2805
2806 wcstring_list_t argv;
2807 for (const auto &c : comps) {
2808 argv.push_back(c.completion);
2809 }
2810
2811 bool bracket = run_one_test_test(expected, argv, true);
2812 bool nonbracket = run_one_test_test(expected, argv, false);
2813 do_test(bracket == nonbracket);
2814 return nonbracket;
2815 }
2816
test_test_brackets()2817 static void test_test_brackets() {
2818 // Ensure [ knows it needs a ].
2819 parser_t &parser = parser_t::principal_parser();
2820 null_output_stream_t null{};
2821 io_streams_t streams(null, null);
2822
2823 wcstring_list_t args;
2824
2825 const wchar_t *args1[] = {L"[", L"foo", nullptr};
2826 do_test(builtin_test(parser, streams, args1) != 0);
2827
2828 const wchar_t *args2[] = {L"[", L"foo", L"]", nullptr};
2829 do_test(builtin_test(parser, streams, args2) == 0);
2830
2831 const wchar_t *args3[] = {L"[", L"foo", L"]", L"bar", nullptr};
2832 do_test(builtin_test(parser, streams, args3) != 0);
2833 }
2834
test_test()2835 static void test_test() {
2836 say(L"Testing test builtin");
2837 test_test_brackets();
2838
2839 do_test(run_test_test(0, L"5 -ne 6"));
2840 do_test(run_test_test(0, L"5 -eq 5"));
2841 do_test(run_test_test(0, L"0 -eq 0"));
2842 do_test(run_test_test(0, L"-1 -eq -1"));
2843 do_test(run_test_test(0, L"1 -ne -1"));
2844 do_test(run_test_test(1, L"' 2 ' -ne 2"));
2845 do_test(run_test_test(0, L"' 2' -eq 2"));
2846 do_test(run_test_test(0, L"'2 ' -eq 2"));
2847 do_test(run_test_test(0, L"' 2 ' -eq 2"));
2848 do_test(run_test_test(2, L"' 2x' -eq 2"));
2849 do_test(run_test_test(2, L"'' -eq 0"));
2850 do_test(run_test_test(2, L"'' -ne 0"));
2851 do_test(run_test_test(2, L"' ' -eq 0"));
2852 do_test(run_test_test(2, L"' ' -ne 0"));
2853 do_test(run_test_test(2, L"'x' -eq 0"));
2854 do_test(run_test_test(2, L"'x' -ne 0"));
2855 do_test(run_test_test(1, L"-1 -ne -1"));
2856 do_test(run_test_test(0, L"abc != def"));
2857 do_test(run_test_test(1, L"abc = def"));
2858 do_test(run_test_test(0, L"5 -le 10"));
2859 do_test(run_test_test(0, L"10 -le 10"));
2860 do_test(run_test_test(1, L"20 -le 10"));
2861 do_test(run_test_test(0, L"-1 -le 0"));
2862 do_test(run_test_test(1, L"0 -le -1"));
2863 do_test(run_test_test(0, L"15 -ge 10"));
2864 do_test(run_test_test(0, L"15 -ge 10"));
2865 do_test(run_test_test(1, L"! 15 -ge 10"));
2866 do_test(run_test_test(0, L"! ! 15 -ge 10"));
2867
2868 do_test(run_test_test(0, L"0 -ne 1 -a 0 -eq 0"));
2869 do_test(run_test_test(0, L"0 -ne 1 -a -n 5"));
2870 do_test(run_test_test(0, L"-n 5 -a 10 -gt 5"));
2871 do_test(run_test_test(0, L"-n 3 -a -n 5"));
2872
2873 // Test precedence:
2874 // '0 == 0 || 0 == 1 && 0 == 2'
2875 // should be evaluated as:
2876 // '0 == 0 || (0 == 1 && 0 == 2)'
2877 // and therefore true. If it were
2878 // '(0 == 0 || 0 == 1) && 0 == 2'
2879 // it would be false.
2880 do_test(run_test_test(0, L"0 = 0 -o 0 = 1 -a 0 = 2"));
2881 do_test(run_test_test(0, L"-n 5 -o 0 = 1 -a 0 = 2"));
2882 do_test(run_test_test(1, L"\\( 0 = 0 -o 0 = 1 \\) -a 0 = 2"));
2883 do_test(run_test_test(0, L"0 = 0 -o \\( 0 = 1 -a 0 = 2 \\)"));
2884
2885 // A few lame tests for permissions; these need to be a lot more complete.
2886 do_test(run_test_test(0, L"-e /bin/ls"));
2887 do_test(run_test_test(1, L"-e /bin/ls_not_a_path"));
2888 do_test(run_test_test(0, L"-x /bin/ls"));
2889 do_test(run_test_test(1, L"-x /bin/ls_not_a_path"));
2890 do_test(run_test_test(0, L"-d /bin/"));
2891 do_test(run_test_test(1, L"-d /bin/ls"));
2892
2893 // This failed at one point.
2894 do_test(run_test_test(1, L"-d /bin -a 5 -eq 3"));
2895 do_test(run_test_test(0, L"-d /bin -o 5 -eq 3"));
2896 do_test(run_test_test(0, L"-d /bin -a ! 5 -eq 3"));
2897
2898 // We didn't properly handle multiple "just strings" either.
2899 do_test(run_test_test(0, L"foo"));
2900 do_test(run_test_test(0, L"foo -a bar"));
2901
2902 // These should be errors.
2903 do_test(run_test_test(1, L"foo bar"));
2904 do_test(run_test_test(1, L"foo bar baz"));
2905
2906 // This crashed.
2907 do_test(run_test_test(1, L"1 = 1 -a = 1"));
2908
2909 // Make sure we can treat -S as a parameter instead of an operator.
2910 // https://github.com/fish-shell/fish-shell/issues/601
2911 do_test(run_test_test(0, L"-S = -S"));
2912 do_test(run_test_test(1, L"! ! ! A"));
2913
2914 // Verify that 1. doubles are treated as doubles, and 2. integers that cannot be represented as
2915 // doubles are still treated as integers.
2916 do_test(run_test_test(0, L"4611686018427387904 -eq 4611686018427387904"));
2917 do_test(run_test_test(0, L"4611686018427387904.0 -eq 4611686018427387904.0"));
2918 do_test(run_test_test(0, L"4611686018427387904.00000000000000001 -eq 4611686018427387904.0"));
2919 do_test(run_test_test(1, L"4611686018427387904 -eq 4611686018427387905"));
2920 do_test(run_test_test(0, L"-4611686018427387904 -ne 4611686018427387904"));
2921 do_test(run_test_test(0, L"-4611686018427387904 -le 4611686018427387904"));
2922 do_test(run_test_test(1, L"-4611686018427387904 -ge 4611686018427387904"));
2923 do_test(run_test_test(1, L"4611686018427387904 -gt 4611686018427387904"));
2924 do_test(run_test_test(0, L"4611686018427387904 -ge 4611686018427387904"));
2925
2926 // test out-of-range numbers
2927 do_test(run_test_test(2, L"99999999999999999999999999 -ge 1"));
2928 do_test(run_test_test(2, L"1 -eq -99999999999999999999999999.9"));
2929 }
2930
test_wcstod()2931 static void test_wcstod() {
2932 say(L"Testing fish_wcstod");
2933 auto tod_test = [](const wchar_t *a, const char *b) {
2934 char *narrow_end = nullptr;
2935 wchar_t *wide_end = nullptr;
2936 double val1 = std::wcstod(a, &wide_end);
2937 double val2 = strtod(b, &narrow_end);
2938 do_test((std::isnan(val1) && std::isnan(val2)) || fabs(val1 - val2) <= __DBL_EPSILON__);
2939 do_test(wide_end - a == narrow_end - b);
2940 };
2941 tod_test(L"", "");
2942 tod_test(L"1.2", "1.2");
2943 tod_test(L"1.5", "1.5");
2944 tod_test(L"-1000", "-1000");
2945 tod_test(L"0.12345", "0.12345");
2946 tod_test(L"nope", "nope");
2947 }
2948
test_dup2s()2949 static void test_dup2s() {
2950 using std::make_shared;
2951 io_chain_t chain;
2952 chain.push_back(make_shared<io_close_t>(17));
2953 chain.push_back(make_shared<io_fd_t>(3, 19));
2954 auto list = dup2_list_t::resolve_chain(chain);
2955 do_test(list.get_actions().size() == 2);
2956
2957 auto act1 = list.get_actions().at(0);
2958 do_test(act1.src == 17);
2959 do_test(act1.target == -1);
2960
2961 auto act2 = list.get_actions().at(1);
2962 do_test(act2.src == 19);
2963 do_test(act2.target == 3);
2964 }
2965
test_dup2s_fd_for_target_fd()2966 static void test_dup2s_fd_for_target_fd() {
2967 using std::make_shared;
2968 io_chain_t chain;
2969 // note io_fd_t params are backwards from dup2.
2970 chain.push_back(make_shared<io_close_t>(10));
2971 chain.push_back(make_shared<io_fd_t>(9, 10));
2972 chain.push_back(make_shared<io_fd_t>(5, 8));
2973 chain.push_back(make_shared<io_fd_t>(1, 4));
2974 chain.push_back(make_shared<io_fd_t>(3, 5));
2975 auto list = dup2_list_t::resolve_chain(chain);
2976
2977 do_test(list.fd_for_target_fd(3) == 8);
2978 do_test(list.fd_for_target_fd(5) == 8);
2979 do_test(list.fd_for_target_fd(8) == 8);
2980 do_test(list.fd_for_target_fd(1) == 4);
2981 do_test(list.fd_for_target_fd(4) == 4);
2982 do_test(list.fd_for_target_fd(100) == 100);
2983 do_test(list.fd_for_target_fd(0) == 0);
2984 do_test(list.fd_for_target_fd(-1) == -1);
2985 do_test(list.fd_for_target_fd(9) == -1);
2986 do_test(list.fd_for_target_fd(10) == -1);
2987 }
2988
2989 /// Testing colors.
test_colors()2990 static void test_colors() {
2991 say(L"Testing colors");
2992 do_test(rgb_color_t(L"#FF00A0").is_rgb());
2993 do_test(rgb_color_t(L"FF00A0").is_rgb());
2994 do_test(rgb_color_t(L"#F30").is_rgb());
2995 do_test(rgb_color_t(L"F30").is_rgb());
2996 do_test(rgb_color_t(L"f30").is_rgb());
2997 do_test(rgb_color_t(L"#FF30a5").is_rgb());
2998 do_test(rgb_color_t(L"3f30").is_none());
2999 do_test(rgb_color_t(L"##f30").is_none());
3000 do_test(rgb_color_t(L"magenta").is_named());
3001 do_test(rgb_color_t(L"MaGeNTa").is_named());
3002 do_test(rgb_color_t(L"mooganta").is_none());
3003 }
3004
3005 // This class allows accessing private bits of autoload_t.
3006 struct autoload_tester_t {
runautoload_tester_t3007 static void run(const wchar_t *fmt, ...) {
3008 va_list va;
3009 va_start(va, fmt);
3010 wcstring cmd = vformat_string(fmt, va);
3011 va_end(va);
3012
3013 int status = system(wcs2string(cmd).c_str());
3014 do_test(status == 0);
3015 }
3016
touch_fileautoload_tester_t3017 static void touch_file(const wcstring &path) {
3018 int fd = wopen_cloexec(path, O_RDWR | O_CREAT, 0666);
3019 do_test(fd >= 0);
3020 write_loop(fd, "Hello", 5);
3021 close(fd);
3022 }
3023
run_testautoload_tester_t3024 static void run_test() {
3025 char t1[] = "/tmp/fish_test_autoload.XXXXXX";
3026 wcstring p1 = str2wcstring(mkdtemp(t1));
3027 char t2[] = "/tmp/fish_test_autoload.XXXXXX";
3028 wcstring p2 = str2wcstring(mkdtemp(t2));
3029
3030 const wcstring_list_t paths = {p1, p2};
3031
3032 autoload_t autoload(L"test_var");
3033 do_test(!autoload.resolve_command(L"file1", paths));
3034 do_test(!autoload.resolve_command(L"nothing", paths));
3035 do_test(autoload.get_autoloaded_commands().empty());
3036
3037 run(L"touch %ls/file1.fish", p1.c_str());
3038 run(L"touch %ls/file2.fish", p2.c_str());
3039 autoload.invalidate_cache();
3040
3041 do_test(!autoload.autoload_in_progress(L"file1"));
3042 do_test(autoload.resolve_command(L"file1", paths));
3043 do_test(!autoload.resolve_command(L"file1", paths));
3044 do_test(autoload.autoload_in_progress(L"file1"));
3045 do_test(autoload.get_autoloaded_commands() == wcstring_list_t{L"file1"});
3046 autoload.mark_autoload_finished(L"file1");
3047 do_test(!autoload.autoload_in_progress(L"file1"));
3048 do_test(autoload.get_autoloaded_commands() == wcstring_list_t{L"file1"});
3049
3050 do_test(!autoload.resolve_command(L"file1", paths));
3051 do_test(!autoload.resolve_command(L"nothing", paths));
3052 do_test(autoload.resolve_command(L"file2", paths));
3053 do_test(!autoload.resolve_command(L"file2", paths));
3054 autoload.mark_autoload_finished(L"file2");
3055 do_test(!autoload.resolve_command(L"file2", paths));
3056 do_test((autoload.get_autoloaded_commands() == wcstring_list_t{L"file1", L"file2"}));
3057
3058 autoload.clear();
3059 do_test(autoload.resolve_command(L"file1", paths));
3060 autoload.mark_autoload_finished(L"file1");
3061 do_test(!autoload.resolve_command(L"file1", paths));
3062 do_test(!autoload.resolve_command(L"nothing", paths));
3063 do_test(autoload.resolve_command(L"file2", paths));
3064 do_test(!autoload.resolve_command(L"file2", paths));
3065 autoload.mark_autoload_finished(L"file2");
3066
3067 do_test(!autoload.resolve_command(L"file1", paths));
3068 touch_file(format_string(L"%ls/file1.fish", p1.c_str()));
3069 autoload.invalidate_cache();
3070 do_test(autoload.resolve_command(L"file1", paths));
3071 autoload.mark_autoload_finished(L"file1");
3072
3073 run(L"rm -Rf %ls", p1.c_str());
3074 run(L"rm -Rf %ls", p2.c_str());
3075 }
3076 };
3077
test_autoload()3078 static void test_autoload() {
3079 say(L"Testing autoload");
3080 autoload_tester_t::run_test();
3081 }
3082
test_complete()3083 static void test_complete() {
3084 say(L"Testing complete");
3085
3086 struct test_complete_vars_t : environment_t {
3087 wcstring_list_t get_names(int flags) const override {
3088 UNUSED(flags);
3089 return {L"Foo1", L"Foo2", L"Foo3", L"Bar1", L"Bar2",
3090 L"Bar3", L"alpha", L"ALPHA!", L"gamma1", L"GAMMA2"};
3091 }
3092
3093 maybe_t<env_var_t> get(const wcstring &key,
3094 env_mode_flags_t mode = ENV_DEFAULT) const override {
3095 UNUSED(mode);
3096 if (key == L"PWD") {
3097 return env_var_t{wgetcwd(), 0};
3098 }
3099 return {};
3100 }
3101 };
3102 test_complete_vars_t vars;
3103
3104 auto parser = parser_t::principal_parser().shared();
3105
3106 auto do_complete = [&](const wcstring &cmd, completion_request_flags_t flags) {
3107 return complete(cmd, flags, operation_context_t{parser, vars, no_cancel});
3108 };
3109
3110 completion_list_t completions;
3111
3112 completions = do_complete(L"$", {});
3113 completions_sort_and_prioritize(&completions);
3114 do_test(completions.size() == 10);
3115 do_test(completions.at(0).completion == L"alpha");
3116 do_test(completions.at(1).completion == L"ALPHA!");
3117 do_test(completions.at(2).completion == L"Bar1");
3118 do_test(completions.at(3).completion == L"Bar2");
3119 do_test(completions.at(4).completion == L"Bar3");
3120 do_test(completions.at(5).completion == L"Foo1");
3121 do_test(completions.at(6).completion == L"Foo2");
3122 do_test(completions.at(7).completion == L"Foo3");
3123 do_test(completions.at(8).completion == L"gamma1");
3124 do_test(completions.at(9).completion == L"GAMMA2");
3125
3126 // Smartcase test. Lowercase inputs match both lowercase and uppercase.
3127 completions = do_complete(L"$a", {});
3128 completions_sort_and_prioritize(&completions);
3129 do_test(completions.size() == 2);
3130 do_test(completions.at(0).completion == L"$ALPHA!");
3131 do_test(completions.at(1).completion == L"lpha");
3132
3133 completions = do_complete(L"$F", {});
3134 completions_sort_and_prioritize(&completions);
3135 do_test(completions.size() == 3);
3136 do_test(completions.at(0).completion == L"oo1");
3137 do_test(completions.at(1).completion == L"oo2");
3138 do_test(completions.at(2).completion == L"oo3");
3139
3140 completions = do_complete(L"$1", {});
3141 completions_sort_and_prioritize(&completions);
3142 do_test(completions.empty());
3143
3144 completions = do_complete(L"$1", completion_request_t::fuzzy_match);
3145 completions_sort_and_prioritize(&completions);
3146 do_test(completions.size() == 3);
3147 do_test(completions.at(0).completion == L"$Bar1");
3148 do_test(completions.at(1).completion == L"$Foo1");
3149 do_test(completions.at(2).completion == L"$gamma1");
3150
3151 if (system("mkdir -p 'test/complete_test'")) err(L"mkdir failed");
3152 if (system("touch 'test/complete_test/has space'")) err(L"touch failed");
3153 if (system("touch 'test/complete_test/bracket[abc]'")) err(L"touch failed");
3154 #ifndef __CYGWIN__ // Square brackets are not legal path characters on WIN32/CYGWIN
3155 if (system(R"(touch 'test/complete_test/gnarlybracket\[abc]')")) err(L"touch failed");
3156 #endif
3157 if (system("touch 'test/complete_test/testfile'")) err(L"touch failed");
3158 if (system("chmod 700 'test/complete_test/testfile'")) err(L"chmod failed");
3159 if (system("mkdir -p 'test/complete_test/foo1'")) err(L"mkdir failed");
3160 if (system("mkdir -p 'test/complete_test/foo2'")) err(L"mkdir failed");
3161 if (system("mkdir -p 'test/complete_test/foo3'")) err(L"mkdir failed");
3162
3163 completions = do_complete(L"echo (test/complete_test/testfil", {});
3164 do_test(completions.size() == 1);
3165 do_test(completions.at(0).completion == L"e");
3166
3167 completions = do_complete(L"echo (ls test/complete_test/testfil", {});
3168 do_test(completions.size() == 1);
3169 do_test(completions.at(0).completion == L"e");
3170
3171 completions = do_complete(L"echo (command ls test/complete_test/testfil", {});
3172 do_test(completions.size() == 1);
3173 do_test(completions.at(0).completion == L"e");
3174
3175 // Completing after spaces - see #2447
3176 completions = do_complete(L"echo (ls test/complete_test/has\\ ", {});
3177 do_test(completions.size() == 1);
3178 do_test(completions.at(0).completion == L"space");
3179
3180 // Brackets - see #5831
3181 completions = do_complete(L"echo (ls test/complete_test/bracket[", {});
3182 do_test(completions.size() == 1);
3183 do_test(completions.at(0).completion == L"test/complete_test/bracket[abc]");
3184
3185 wcstring cmdline = L"touch test/complete_test/bracket[";
3186 completions = do_complete(cmdline, {});
3187 do_test(completions.size() == 1);
3188 do_test(completions.front().completion == L"test/complete_test/bracket[abc]");
3189 size_t where = cmdline.size();
3190 wcstring newcmdline = completion_apply_to_command_line(
3191 completions.front().completion, completions.front().flags, cmdline, &where, false);
3192 do_test(newcmdline == L"touch test/complete_test/bracket\\[abc\\] ");
3193
3194 #ifndef __CYGWIN__ // Square brackets are not legal path characters on WIN32/CYGWIN
3195 cmdline = LR"(touch test/complete_test/gnarlybracket\\[)";
3196 completions = do_complete(cmdline, {});
3197 do_test(completions.size() == 1);
3198 do_test(completions.front().completion == LR"(test/complete_test/gnarlybracket\[abc])");
3199 where = cmdline.size();
3200 newcmdline = completion_apply_to_command_line(
3201 completions.front().completion, completions.front().flags, cmdline, &where, false);
3202 do_test(newcmdline == LR"(touch test/complete_test/gnarlybracket\\\[abc\] )");
3203 #endif
3204
3205 // Add a function and test completing it in various ways.
3206 // Note we're depending on function_add not complaining when given missing parsed_source /
3207 // body_node.
3208 function_add(L"scuttlebutt", {}, nullptr, {});
3209
3210 // Complete a function name.
3211 completions = do_complete(L"echo (scuttlebut", {});
3212 do_test(completions.size() == 1);
3213 do_test(completions.at(0).completion == L"t");
3214
3215 // But not with the command prefix.
3216 completions = do_complete(L"echo (command scuttlebut", {});
3217 do_test(completions.size() == 0);
3218
3219 // Not with the builtin prefix.
3220 completions = do_complete(L"echo (builtin scuttlebut", {});
3221 do_test(completions.size() == 0);
3222
3223 // Not after a redirection.
3224 completions = do_complete(L"echo hi > scuttlebut", {});
3225 do_test(completions.size() == 0);
3226
3227 // Trailing spaces (#1261).
3228 completion_mode_t no_files{};
3229 no_files.no_files = true;
3230 complete_add(L"foobarbaz", false, wcstring(), option_type_args_only, no_files, NULL, L"qux",
3231 NULL, COMPLETE_AUTO_SPACE);
3232 completions = do_complete(L"foobarbaz ", {});
3233 do_test(completions.size() == 1);
3234 do_test(completions.at(0).completion == L"qux");
3235
3236 // Don't complete variable names in single quotes (#1023).
3237 completions = do_complete(L"echo '$Foo", {});
3238 do_test(completions.empty());
3239 completions = do_complete(L"echo \\$Foo", {});
3240 do_test(completions.empty());
3241
3242 // File completions.
3243 completions = do_complete(L"cat test/complete_test/te", {});
3244 do_test(completions.size() == 1);
3245 do_test(completions.at(0).completion == L"stfile");
3246 completions = do_complete(L"echo sup > test/complete_test/te", {});
3247 do_test(completions.size() == 1);
3248 do_test(completions.at(0).completion == L"stfile");
3249 completions = do_complete(L"echo sup > test/complete_test/te", {});
3250 do_test(completions.size() == 1);
3251 do_test(completions.at(0).completion == L"stfile");
3252
3253 if (!pushd("test/complete_test")) return;
3254 completions = do_complete(L"cat te", {});
3255 do_test(completions.size() == 1);
3256 do_test(completions.at(0).completion == L"stfile");
3257 do_test(!(completions.at(0).flags & COMPLETE_REPLACES_TOKEN));
3258 do_test(!(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT));
3259 completions = do_complete(L"cat testfile te", {});
3260 do_test(completions.size() == 1);
3261 do_test(completions.at(0).completion == L"stfile");
3262 do_test(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT);
3263 completions = do_complete(L"cat testfile TE", {});
3264 do_test(completions.size() == 1);
3265 do_test(completions.at(0).completion == L"testfile");
3266 do_test(completions.at(0).flags & COMPLETE_REPLACES_TOKEN);
3267 do_test(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT);
3268 completions = do_complete(L"something --abc=te", {});
3269 do_test(completions.size() == 1);
3270 do_test(completions.at(0).completion == L"stfile");
3271 completions = do_complete(L"something -abc=te", {});
3272 do_test(completions.size() == 1);
3273 do_test(completions.at(0).completion == L"stfile");
3274 completions = do_complete(L"something abc=te", {});
3275 do_test(completions.size() == 1);
3276 do_test(completions.at(0).completion == L"stfile");
3277 completions = do_complete(L"something abc=stfile", {});
3278 do_test(completions.size() == 0);
3279 completions = do_complete(L"something abc=stfile", completion_request_t::fuzzy_match);
3280 do_test(completions.size() == 1);
3281 do_test(completions.at(0).completion == L"abc=testfile");
3282
3283 // Zero escapes can cause problems. See issue #1631.
3284 completions = do_complete(L"cat foo\\0", {});
3285 do_test(completions.empty());
3286 completions = do_complete(L"cat foo\\0bar", {});
3287 do_test(completions.empty());
3288 completions = do_complete(L"cat \\0", {});
3289 do_test(completions.empty());
3290 completions = do_complete(L"cat te\\0", {});
3291 do_test(completions.empty());
3292
3293 popd();
3294 completions.clear();
3295
3296 // Test abbreviations.
3297 auto &pvars = parser_t::principal_parser().vars();
3298 function_add(L"testabbrsonetwothreefour", {}, nullptr, {});
3299 int ret = pvars.set_one(L"_fish_abbr_testabbrsonetwothreezero", ENV_LOCAL, L"expansion");
3300 completions = complete(L"testabbrsonetwothree", {}, parser->context());
3301 do_test(ret == 0);
3302 do_test(completions.size() == 2);
3303 do_test(completions.at(0).completion == L"four");
3304 do_test((completions.at(0).flags & COMPLETE_NO_SPACE) == 0);
3305 // Abbreviations should not have a space after them.
3306 do_test(completions.at(1).completion == L"zero");
3307 do_test((completions.at(1).flags & COMPLETE_NO_SPACE) != 0);
3308
3309 // Test wraps.
3310 do_test(comma_join(complete_get_wrap_targets(L"wrapper1")).empty());
3311 complete_add_wrapper(L"wrapper1", L"wrapper2");
3312 do_test(comma_join(complete_get_wrap_targets(L"wrapper1")) == L"wrapper2");
3313 complete_add_wrapper(L"wrapper2", L"wrapper3");
3314 do_test(comma_join(complete_get_wrap_targets(L"wrapper1")) == L"wrapper2");
3315 do_test(comma_join(complete_get_wrap_targets(L"wrapper2")) == L"wrapper3");
3316 complete_add_wrapper(L"wrapper3", L"wrapper1"); // loop!
3317 do_test(comma_join(complete_get_wrap_targets(L"wrapper1")) == L"wrapper2");
3318 do_test(comma_join(complete_get_wrap_targets(L"wrapper2")) == L"wrapper3");
3319 do_test(comma_join(complete_get_wrap_targets(L"wrapper3")) == L"wrapper1");
3320 complete_remove_wrapper(L"wrapper1", L"wrapper2");
3321 do_test(comma_join(complete_get_wrap_targets(L"wrapper1")).empty());
3322 do_test(comma_join(complete_get_wrap_targets(L"wrapper2")) == L"wrapper3");
3323 do_test(comma_join(complete_get_wrap_targets(L"wrapper3")) == L"wrapper1");
3324
3325 // Test cd wrapping chain
3326 if (!pushd("test/complete_test")) err(L"pushd(\"test/complete_test\") failed");
3327
3328 complete_add_wrapper(L"cdwrap1", L"cd");
3329 complete_add_wrapper(L"cdwrap2", L"cdwrap1");
3330
3331 completion_list_t cd_compl = do_complete(L"cd ", {});
3332 completions_sort_and_prioritize(&cd_compl);
3333
3334 completion_list_t cdwrap1_compl = do_complete(L"cdwrap1 ", {});
3335 completions_sort_and_prioritize(&cdwrap1_compl);
3336
3337 completion_list_t cdwrap2_compl = do_complete(L"cdwrap2 ", {});
3338 completions_sort_and_prioritize(&cdwrap2_compl);
3339
3340 size_t min_compl_size =
3341 std::min(cd_compl.size(), std::min(cdwrap1_compl.size(), cdwrap2_compl.size()));
3342
3343 do_test(cd_compl.size() == min_compl_size);
3344 do_test(cdwrap1_compl.size() == min_compl_size);
3345 do_test(cdwrap2_compl.size() == min_compl_size);
3346 for (size_t i = 0; i < min_compl_size; ++i) {
3347 do_test(cd_compl[i].completion == cdwrap1_compl[i].completion);
3348 do_test(cdwrap1_compl[i].completion == cdwrap2_compl[i].completion);
3349 }
3350
3351 complete_remove_wrapper(L"cdwrap1", L"cd");
3352 complete_remove_wrapper(L"cdwrap2", L"cdwrap1");
3353 popd();
3354 }
3355
test_1_completion(wcstring line,const wcstring & completion,complete_flags_t flags,bool append_only,wcstring expected,long source_line)3356 static void test_1_completion(wcstring line, const wcstring &completion, complete_flags_t flags,
3357 bool append_only, wcstring expected, long source_line) {
3358 // str is given with a caret, which we use to represent the cursor position. Find it.
3359 const size_t in_cursor_pos = line.find(L'^');
3360 do_test(in_cursor_pos != wcstring::npos);
3361 line.erase(in_cursor_pos, 1);
3362
3363 const size_t out_cursor_pos = expected.find(L'^');
3364 do_test(out_cursor_pos != wcstring::npos);
3365 expected.erase(out_cursor_pos, 1);
3366
3367 size_t cursor_pos = in_cursor_pos;
3368 wcstring result =
3369 completion_apply_to_command_line(completion, flags, line, &cursor_pos, append_only);
3370 if (result != expected) {
3371 std::fwprintf(stderr, L"line %ld: %ls + %ls -> [%ls], expected [%ls]\n", source_line,
3372 line.c_str(), completion.c_str(), result.c_str(), expected.c_str());
3373 }
3374 do_test(result == expected);
3375 do_test(cursor_pos == out_cursor_pos);
3376 }
3377
test_wait_handles()3378 static void test_wait_handles() {
3379 say(L"Testing wait handles");
3380 constexpr size_t limit = 4;
3381 wait_handle_store_t whs(limit);
3382 do_test(whs.size() == 0);
3383
3384 // Null handles ignored.
3385 whs.add(wait_handle_ref_t{});
3386 do_test(whs.size() == 0);
3387 do_test(whs.get_by_pid(5) == nullptr);
3388
3389 // Duplicate pids drop oldest.
3390 whs.add(std::make_shared<wait_handle_t>(5, L"first"));
3391 whs.add(std::make_shared<wait_handle_t>(5, L"second"));
3392 do_test(whs.size() == 1);
3393 do_test(whs.get_by_pid(5)->base_name == L"second");
3394
3395 whs.remove_by_pid(123);
3396 do_test(whs.size() == 1);
3397 whs.remove_by_pid(5);
3398 do_test(whs.size() == 0);
3399
3400 // Test evicting oldest.
3401 whs.add(std::make_shared<wait_handle_t>(1, L"1"));
3402 whs.add(std::make_shared<wait_handle_t>(2, L"2"));
3403 whs.add(std::make_shared<wait_handle_t>(3, L"3"));
3404 whs.add(std::make_shared<wait_handle_t>(4, L"4"));
3405 whs.add(std::make_shared<wait_handle_t>(5, L"5"));
3406 do_test(whs.size() == 4);
3407 auto start = whs.get_list().begin();
3408 do_test(std::next(start, 0)->get()->base_name == L"5");
3409 do_test(std::next(start, 1)->get()->base_name == L"4");
3410 do_test(std::next(start, 2)->get()->base_name == L"3");
3411 do_test(std::next(start, 3)->get()->base_name == L"2");
3412 }
3413
test_completion_insertions()3414 static void test_completion_insertions() {
3415 #define TEST_1_COMPLETION(a, b, c, d, e) test_1_completion(a, b, c, d, e, __LINE__)
3416 say(L"Testing completion insertions");
3417 TEST_1_COMPLETION(L"foo^", L"bar", 0, false, L"foobar ^");
3418 // An unambiguous completion of a token that is already trailed by a space character.
3419 // After completing, the cursor moves on to the next token, suggesting to the user that the
3420 // current token is finished.
3421 TEST_1_COMPLETION(L"foo^ baz", L"bar", 0, false, L"foobar ^baz");
3422 TEST_1_COMPLETION(L"'foo^", L"bar", 0, false, L"'foobar' ^");
3423 TEST_1_COMPLETION(L"'foo'^", L"bar", 0, false, L"'foobar' ^");
3424 TEST_1_COMPLETION(L"'foo\\'^", L"bar", 0, false, L"'foo\\'bar' ^");
3425 TEST_1_COMPLETION(L"foo\\'^", L"bar", 0, false, L"foo\\'bar ^");
3426
3427 // Test append only.
3428 TEST_1_COMPLETION(L"foo^", L"bar", 0, true, L"foobar ^");
3429 TEST_1_COMPLETION(L"foo^ baz", L"bar", 0, true, L"foobar ^baz");
3430 TEST_1_COMPLETION(L"'foo^", L"bar", 0, true, L"'foobar' ^");
3431 TEST_1_COMPLETION(L"'foo'^", L"bar", 0, true, L"'foo'bar ^");
3432 TEST_1_COMPLETION(L"'foo\\'^", L"bar", 0, true, L"'foo\\'bar' ^");
3433 TEST_1_COMPLETION(L"foo\\'^", L"bar", 0, true, L"foo\\'bar ^");
3434
3435 TEST_1_COMPLETION(L"foo^", L"bar", COMPLETE_NO_SPACE, false, L"foobar^");
3436 TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_NO_SPACE, false, L"'foobar^");
3437 TEST_1_COMPLETION(L"'foo'^", L"bar", COMPLETE_NO_SPACE, false, L"'foobar'^");
3438 TEST_1_COMPLETION(L"'foo\\'^", L"bar", COMPLETE_NO_SPACE, false, L"'foo\\'bar^");
3439 TEST_1_COMPLETION(L"foo\\'^", L"bar", COMPLETE_NO_SPACE, false, L"foo\\'bar^");
3440
3441 TEST_1_COMPLETION(L"foo^", L"bar", COMPLETE_REPLACES_TOKEN, false, L"bar ^");
3442 TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_REPLACES_TOKEN, false, L"bar ^");
3443
3444 // See #6130
3445 TEST_1_COMPLETION(L": (:^ ''", L"", 0, false, L": (: ^''");
3446 }
3447
perform_one_autosuggestion_cd_test(const wcstring & command,const wcstring & expected,const environment_t & vars,long line)3448 static void perform_one_autosuggestion_cd_test(const wcstring &command, const wcstring &expected,
3449 const environment_t &vars, long line) {
3450 completion_list_t comps =
3451 complete(command, completion_request_t::autosuggestion, operation_context_t{vars});
3452
3453 bool expects_error = (expected == L"<error>");
3454
3455 if (comps.empty() && !expects_error) {
3456 std::fwprintf(stderr, L"line %ld: autosuggest_suggest_special() failed for command %ls\n",
3457 line, command.c_str());
3458 do_test_from(!comps.empty(), line);
3459 return;
3460 } else if (!comps.empty() && expects_error) {
3461 std::fwprintf(stderr,
3462 L"line %ld: autosuggest_suggest_special() was expected to fail but did not, "
3463 L"for command %ls\n",
3464 line, command.c_str());
3465 do_test_from(comps.empty(), line);
3466 }
3467
3468 if (!comps.empty()) {
3469 completions_sort_and_prioritize(&comps);
3470 const completion_t &suggestion = comps.at(0);
3471
3472 if (suggestion.completion != expected) {
3473 std::fwprintf(
3474 stderr,
3475 L"line %ld: complete() for cd returned the wrong expected string for command %ls\n",
3476 line, command.c_str());
3477 std::fwprintf(stderr, L" actual: %ls\n", suggestion.completion.c_str());
3478 std::fwprintf(stderr, L"expected: %ls\n", expected.c_str());
3479 do_test_from(suggestion.completion == expected, line);
3480 }
3481 }
3482 }
3483
perform_one_completion_cd_test(const wcstring & command,const wcstring & expected,const environment_t & vars,long line)3484 static void perform_one_completion_cd_test(const wcstring &command, const wcstring &expected,
3485 const environment_t &vars, long line) {
3486 completion_list_t comps = complete(command, {}, operation_context_t{vars});
3487
3488 bool expects_error = (expected == L"<error>");
3489
3490 if (comps.empty() && !expects_error) {
3491 std::fwprintf(stderr, L"line %ld: autosuggest_suggest_special() failed for command %ls\n",
3492 line, command.c_str());
3493 do_test_from(!comps.empty(), line);
3494 return;
3495 } else if (!comps.empty() && expects_error) {
3496 std::fwprintf(stderr,
3497 L"line %ld: autosuggest_suggest_special() was expected to fail but did not, "
3498 L"for command %ls\n",
3499 line, command.c_str());
3500 do_test_from(comps.empty(), line);
3501 }
3502
3503 if (!comps.empty()) {
3504 completions_sort_and_prioritize(&comps);
3505 const completion_t &suggestion = comps.at(0);
3506
3507 if (suggestion.completion != expected) {
3508 std::fwprintf(stderr,
3509 L"line %ld: complete() for cd tab completion returned the wrong expected "
3510 L"string for command %ls\n",
3511 line, command.c_str());
3512 std::fwprintf(stderr, L" actual: %ls\n", suggestion.completion.c_str());
3513 std::fwprintf(stderr, L"expected: %ls\n", expected.c_str());
3514 do_test_from(suggestion.completion == expected, line);
3515 }
3516 }
3517 }
3518
3519 // Testing test_autosuggest_suggest_special, in particular for properly handling quotes and
3520 // backslashes.
test_autosuggest_suggest_special()3521 static void test_autosuggest_suggest_special() {
3522 if (system("mkdir -p 'test/autosuggest_test/0foobar'")) err(L"mkdir failed");
3523 if (system("mkdir -p 'test/autosuggest_test/1foo bar'")) err(L"mkdir failed");
3524 if (system("mkdir -p 'test/autosuggest_test/2foo bar'")) err(L"mkdir failed");
3525 if (system("mkdir -p 'test/autosuggest_test/3foo\\bar'")) err(L"mkdir failed");
3526 if (system("mkdir -p test/autosuggest_test/4foo\\'bar")) {
3527 err(L"mkdir failed"); // a path with a single quote
3528 }
3529 if (system("mkdir -p test/autosuggest_test/5foo\\\"bar")) {
3530 err(L"mkdir failed"); // a path with a double quote
3531 }
3532 // This is to ensure tilde expansion is handled. See the `cd ~/test_autosuggest_suggest_specia`
3533 // test below.
3534 // Fake out the home directory
3535 parser_t::principal_parser().vars().set_one(L"HOME", ENV_LOCAL | ENV_EXPORT, L"test/test-home");
3536 if (system("mkdir -p test/test-home/test_autosuggest_suggest_special/")) {
3537 err(L"mkdir failed");
3538 }
3539 if (system("mkdir -p test/autosuggest_test/start/unique2/unique3/multi4")) {
3540 err(L"mkdir failed");
3541 }
3542 if (system("mkdir -p test/autosuggest_test/start/unique2/unique3/multi42")) {
3543 err(L"mkdir failed");
3544 }
3545 if (system("mkdir -p test/autosuggest_test/start/unique2/.hiddenDir/moreStuff")) {
3546 err(L"mkdir failed");
3547 }
3548
3549 const wcstring wd = L"test/autosuggest_test";
3550
3551 pwd_environment_t vars{};
3552 vars.vars[L"HOME"] = parser_t::principal_parser().vars().get(L"HOME")->as_string();
3553
3554 perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/0", L"foobar/", vars, __LINE__);
3555 perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/0", L"foobar/", vars, __LINE__);
3556 perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/0", L"foobar/", vars, __LINE__);
3557 perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/1", L"foo bar/", vars, __LINE__);
3558 perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/1", L"foo bar/", vars,
3559 __LINE__);
3560 perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/1", L"foo bar/", vars, __LINE__);
3561 perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/2", L"foo bar/", vars, __LINE__);
3562 perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/2", L"foo bar/", vars,
3563 __LINE__);
3564 perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/2", L"foo bar/", vars,
3565 __LINE__);
3566 perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/3", L"foo\\bar/", vars, __LINE__);
3567 perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/3", L"foo\\bar/", vars,
3568 __LINE__);
3569 perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/3", L"foo\\bar/", vars,
3570 __LINE__);
3571 perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/4", L"foo'bar/", vars, __LINE__);
3572 perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/4", L"foo'bar/", vars,
3573 __LINE__);
3574 perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/4", L"foo'bar/", vars, __LINE__);
3575 perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/5", L"foo\"bar/", vars, __LINE__);
3576 perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/5", L"foo\"bar/", vars,
3577 __LINE__);
3578 perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/5", L"foo\"bar/", vars,
3579 __LINE__);
3580
3581 vars.vars[L"AUTOSUGGEST_TEST_LOC"] = wd;
3582 perform_one_autosuggestion_cd_test(L"cd $AUTOSUGGEST_TEST_LOC/0", L"foobar/", vars, __LINE__);
3583 perform_one_autosuggestion_cd_test(L"cd ~/test_autosuggest_suggest_specia", L"l/", vars,
3584 __LINE__);
3585
3586 perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/start/", L"unique2/unique3/",
3587 vars, __LINE__);
3588
3589 if (!pushd(wcs2string(wd).c_str())) return;
3590 perform_one_autosuggestion_cd_test(L"cd 0", L"foobar/", vars, __LINE__);
3591 perform_one_autosuggestion_cd_test(L"cd \"0", L"foobar/", vars, __LINE__);
3592 perform_one_autosuggestion_cd_test(L"cd '0", L"foobar/", vars, __LINE__);
3593 perform_one_autosuggestion_cd_test(L"cd 1", L"foo bar/", vars, __LINE__);
3594 perform_one_autosuggestion_cd_test(L"cd \"1", L"foo bar/", vars, __LINE__);
3595 perform_one_autosuggestion_cd_test(L"cd '1", L"foo bar/", vars, __LINE__);
3596 perform_one_autosuggestion_cd_test(L"cd 2", L"foo bar/", vars, __LINE__);
3597 perform_one_autosuggestion_cd_test(L"cd \"2", L"foo bar/", vars, __LINE__);
3598 perform_one_autosuggestion_cd_test(L"cd '2", L"foo bar/", vars, __LINE__);
3599 perform_one_autosuggestion_cd_test(L"cd 3", L"foo\\bar/", vars, __LINE__);
3600 perform_one_autosuggestion_cd_test(L"cd \"3", L"foo\\bar/", vars, __LINE__);
3601 perform_one_autosuggestion_cd_test(L"cd '3", L"foo\\bar/", vars, __LINE__);
3602 perform_one_autosuggestion_cd_test(L"cd 4", L"foo'bar/", vars, __LINE__);
3603 perform_one_autosuggestion_cd_test(L"cd \"4", L"foo'bar/", vars, __LINE__);
3604 perform_one_autosuggestion_cd_test(L"cd '4", L"foo'bar/", vars, __LINE__);
3605 perform_one_autosuggestion_cd_test(L"cd 5", L"foo\"bar/", vars, __LINE__);
3606 perform_one_autosuggestion_cd_test(L"cd \"5", L"foo\"bar/", vars, __LINE__);
3607 perform_one_autosuggestion_cd_test(L"cd '5", L"foo\"bar/", vars, __LINE__);
3608
3609 // A single quote should defeat tilde expansion.
3610 perform_one_autosuggestion_cd_test(L"cd '~/test_autosuggest_suggest_specia'", L"<error>", vars,
3611 __LINE__);
3612
3613 // Don't crash on ~ (issue #2696). Note this is cwd dependent.
3614 if (system("mkdir -p '~absolutelynosuchuser/path1/path2/'")) err(L"mkdir failed");
3615 perform_one_autosuggestion_cd_test(L"cd ~absolutelynosuchus", L"er/path1/path2/", vars,
3616 __LINE__);
3617 perform_one_autosuggestion_cd_test(L"cd ~absolutelynosuchuser/", L"path1/path2/", vars,
3618 __LINE__);
3619 perform_one_completion_cd_test(L"cd ~absolutelynosuchus", L"er/", vars, __LINE__);
3620 perform_one_completion_cd_test(L"cd ~absolutelynosuchuser/", L"path1/", vars, __LINE__);
3621
3622 parser_t::principal_parser().vars().remove(L"HOME", ENV_LOCAL | ENV_EXPORT);
3623 popd();
3624 }
3625
perform_one_autosuggestion_should_ignore_test(const wcstring & command,long line)3626 static void perform_one_autosuggestion_should_ignore_test(const wcstring &command, long line) {
3627 completion_list_t comps =
3628 complete(command, completion_request_t::autosuggestion, operation_context_t::empty());
3629 do_test(comps.empty());
3630 if (!comps.empty()) {
3631 const wcstring &suggestion = comps.front().completion;
3632 std::fwprintf(stderr, L"line %ld: complete() expected to return nothing for %ls\n", line,
3633 command.c_str());
3634 std::fwprintf(stderr, L" instead got: %ls\n", suggestion.c_str());
3635 }
3636 }
3637
test_autosuggestion_ignores()3638 static void test_autosuggestion_ignores() {
3639 say(L"Testing scenarios that should produce no autosuggestions");
3640 // Do not do file autosuggestions immediately after certain statement terminators - see #1631.
3641 perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST|", __LINE__);
3642 perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST&", __LINE__);
3643 perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST#comment", __LINE__);
3644 perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST;", __LINE__);
3645 }
3646
test_autosuggestion_combining()3647 static void test_autosuggestion_combining() {
3648 say(L"Testing autosuggestion combining");
3649 do_test(combine_command_and_autosuggestion(L"alpha", L"alphabeta") == L"alphabeta");
3650
3651 // When the last token contains no capital letters, we use the case of the autosuggestion.
3652 do_test(combine_command_and_autosuggestion(L"alpha", L"ALPHABETA") == L"ALPHABETA");
3653
3654 // When the last token contains capital letters, we use its case.
3655 do_test(combine_command_and_autosuggestion(L"alPha", L"alphabeTa") == L"alPhabeTa");
3656
3657 // If autosuggestion is not longer than input, use the input's case.
3658 do_test(combine_command_and_autosuggestion(L"alpha", L"ALPHAA") == L"ALPHAA");
3659 do_test(combine_command_and_autosuggestion(L"alpha", L"ALPHA") == L"alpha");
3660 }
3661
test_history_matches(history_search_t & search,const wcstring_list_t & expected,unsigned from_line)3662 static void test_history_matches(history_search_t &search, const wcstring_list_t &expected,
3663 unsigned from_line) {
3664 wcstring_list_t found;
3665 while (search.go_backwards()) {
3666 found.push_back(search.current_string());
3667 }
3668 do_test_from(expected == found, from_line);
3669 if (expected != found) {
3670 fprintf(stderr, "Expected %ls, found %ls\n", comma_join(expected).c_str(),
3671 comma_join(found).c_str());
3672 }
3673 }
3674
history_contains(history_t * history,const wcstring & txt)3675 static bool history_contains(history_t *history, const wcstring &txt) {
3676 bool result = false;
3677 size_t i;
3678 for (i = 1;; i++) {
3679 history_item_t item = history->item_at_index(i);
3680 if (item.empty()) break;
3681
3682 if (item.str() == txt) {
3683 result = true;
3684 break;
3685 }
3686 }
3687 return result;
3688 }
3689
history_contains(const std::shared_ptr<history_t> & history,const wcstring & txt)3690 static bool history_contains(const std::shared_ptr<history_t> &history, const wcstring &txt) {
3691 return history_contains(history.get(), txt);
3692 }
3693
test_input()3694 static void test_input() {
3695 say(L"Testing input");
3696 inputter_t input{parser_t::principal_parser()};
3697 // Ensure sequences are order independent. Here we add two bindings where the first is a prefix
3698 // of the second, and then emit the second key list. The second binding should be invoked, not
3699 // the first!
3700 wcstring prefix_binding = L"qqqqqqqa";
3701 wcstring desired_binding = prefix_binding + L'a';
3702
3703 {
3704 auto input_mapping = input_mappings();
3705 input_mapping->add(prefix_binding, L"up-line");
3706 input_mapping->add(desired_binding, L"down-line");
3707 }
3708
3709 // Push the desired binding to the queue.
3710 for (wchar_t c : desired_binding) {
3711 input.queue_char(c);
3712 }
3713
3714 // Now test.
3715 auto evt = input.read_char();
3716 if (!evt.is_readline()) {
3717 err(L"Event is not a readline");
3718 } else if (evt.get_readline() != readline_cmd_t::down_line) {
3719 err(L"Expected to read char down_line");
3720 }
3721 }
3722
test_line_iterator()3723 static void test_line_iterator() {
3724 say(L"Testing line iterator");
3725
3726 std::string text1 = "Alpha\nBeta\nGamma\n\nDelta\n";
3727 std::vector<std::string> lines1;
3728 line_iterator_t<std::string> iter1(text1);
3729 while (iter1.next()) lines1.push_back(iter1.line());
3730 do_test((lines1 == std::vector<std::string>{"Alpha", "Beta", "Gamma", "", "Delta"}));
3731
3732 wcstring text2 = L"\n\nAlpha\nBeta\nGamma\n\nDelta";
3733 wcstring_list_t lines2;
3734 line_iterator_t<wcstring> iter2(text2);
3735 while (iter2.next()) lines2.push_back(iter2.line());
3736 do_test((lines2 == wcstring_list_t{L"", L"", L"Alpha", L"Beta", L"Gamma", L"", L"Delta"}));
3737 }
3738
test_undo()3739 static void test_undo() {
3740 say(L"Testing undo/redo setting and restoring text and cursor position.");
3741
3742 editable_line_t line;
3743 do_test(!line.undo()); // nothing to undo
3744 do_test(line.text() == L"");
3745 do_test(line.position() == 0);
3746 line.push_edit(edit_t(0, 0, L"a b c"));
3747 do_test(line.text() == L"a b c");
3748 do_test(line.position() == 5);
3749 line.set_position(2);
3750 line.push_edit(edit_t(2, 1, L"B")); // replacement right of cursor
3751 do_test(line.text() == L"a B c");
3752 line.undo();
3753 do_test(line.text() == L"a b c");
3754 do_test(line.position() == 2);
3755 line.redo();
3756 do_test(line.text() == L"a B c");
3757 do_test(line.position() == 3);
3758
3759 do_test(!line.redo()); // nothing to redo
3760
3761 line.push_edit(edit_t(0, 2, L"")); // deletion left of cursor
3762 do_test(line.text() == L"B c");
3763 do_test(line.position() == 1);
3764 line.undo();
3765 do_test(line.text() == L"a B c");
3766 do_test(line.position() == 3);
3767 line.redo();
3768 do_test(line.text() == L"B c");
3769 do_test(line.position() == 1);
3770
3771 line.push_edit(edit_t(0, line.size(), L"a b c")); // replacement left and right of cursor
3772 do_test(line.text() == L"a b c");
3773 do_test(line.position() == 5);
3774
3775 say(L"Testing undoing coalesced edits.");
3776 line.clear();
3777 line.push_edit(edit_t(line.position(), 0, L"a"));
3778 line.insert_coalesce(L"b");
3779 line.insert_coalesce(L"c");
3780 do_test(line.undo_history.edits.size() == 1);
3781 line.push_edit(edit_t(line.position(), 0, L" "));
3782 do_test(line.undo_history.edits.size() == 2);
3783 line.undo();
3784 line.undo();
3785 line.redo();
3786 do_test(line.text() == L"abc");
3787 do_test(line.undo_history.edits.size() == 2);
3788 // This removes the space insertion from the history, but does not coalesce with the first edit.
3789 line.push_edit(edit_t(line.position(), 0, L"d"));
3790 do_test(line.undo_history.edits.size() == 2);
3791 line.insert_coalesce(L"e");
3792 do_test(line.text() == L"abcde");
3793 line.undo();
3794 do_test(line.text() == L"abc");
3795 }
3796
3797 #define UVARS_PER_THREAD 8
3798 #define UVARS_TEST_PATH L"test/fish_uvars_test/varsfile.txt"
3799
test_universal_helper(int x)3800 static int test_universal_helper(int x) {
3801 callback_data_list_t callbacks;
3802 env_universal_t uvars;
3803 uvars.initialize_at_path(callbacks, UVARS_TEST_PATH);
3804 for (int j = 0; j < UVARS_PER_THREAD; j++) {
3805 const wcstring key = format_string(L"key_%d_%d", x, j);
3806 const wcstring val = format_string(L"val_%d_%d", x, j);
3807 uvars.set(key, env_var_t{val, 0});
3808 bool synced = uvars.sync(callbacks);
3809 if (!synced) {
3810 err(L"Failed to sync universal variables after modification");
3811 }
3812 }
3813
3814 // Last step is to delete the first key.
3815 uvars.remove(format_string(L"key_%d_%d", x, 0));
3816 bool synced = uvars.sync(callbacks);
3817 if (!synced) {
3818 err(L"Failed to sync universal variables after deletion");
3819 }
3820 return 0;
3821 }
3822
test_universal()3823 static void test_universal() {
3824 say(L"Testing universal variables");
3825 if (system("mkdir -p test/fish_uvars_test/")) err(L"mkdir failed");
3826
3827 const int threads = 1;
3828 for (int i = 0; i < threads; i++) {
3829 iothread_perform([=]() { test_universal_helper(i); });
3830 }
3831 iothread_drain_all();
3832
3833 env_universal_t uvars;
3834 callback_data_list_t callbacks;
3835 uvars.initialize_at_path(callbacks, UVARS_TEST_PATH);
3836 for (int i = 0; i < threads; i++) {
3837 for (int j = 0; j < UVARS_PER_THREAD; j++) {
3838 const wcstring key = format_string(L"key_%d_%d", i, j);
3839 maybe_t<env_var_t> expected_val;
3840 if (j == 0) {
3841 expected_val = none();
3842 } else {
3843 expected_val = env_var_t(format_string(L"val_%d_%d", i, j), 0);
3844 }
3845 const maybe_t<env_var_t> var = uvars.get(key);
3846 if (j == 0) assert(!expected_val);
3847 if (var != expected_val) {
3848 const wchar_t *missing_desc = L"<missing>";
3849 err(L"Wrong value for key %ls: expected %ls, got %ls\n", key.c_str(),
3850 (expected_val ? expected_val->as_string().c_str() : missing_desc),
3851 (var ? var->as_string().c_str() : missing_desc));
3852 }
3853 }
3854 }
3855 system_assert("rm -Rf test/fish_uvars_test/");
3856 }
3857
test_universal_output()3858 static void test_universal_output() {
3859 say(L"Testing universal variable output");
3860
3861 const env_var_t::env_var_flags_t flag_export = env_var_t::flag_export;
3862 const env_var_t::env_var_flags_t flag_pathvar = env_var_t::flag_pathvar;
3863
3864 var_table_t vars;
3865 vars[L"varA"] = env_var_t(wcstring_list_t{L"ValA1", L"ValA2"}, 0);
3866 vars[L"varB"] = env_var_t(wcstring_list_t{L"ValB1"}, flag_export);
3867 vars[L"varC"] = env_var_t(wcstring_list_t{L"ValC1"}, 0);
3868 vars[L"varD"] = env_var_t(wcstring_list_t{L"ValD1"}, flag_export | flag_pathvar);
3869 vars[L"varE"] = env_var_t(wcstring_list_t{L"ValE1", L"ValE2"}, flag_pathvar);
3870
3871 std::string text = env_universal_t::serialize_with_vars(vars);
3872 const char *expected =
3873 "# This file contains fish universal variable definitions.\n"
3874 "# VERSION: 3.0\n"
3875 "SETUVAR varA:ValA1\\x1eValA2\n"
3876 "SETUVAR --export varB:ValB1\n"
3877 "SETUVAR varC:ValC1\n"
3878 "SETUVAR --export --path varD:ValD1\n"
3879 "SETUVAR --path varE:ValE1\\x1eValE2\n";
3880 do_test(text == expected);
3881 }
3882
test_universal_parsing()3883 static void test_universal_parsing() {
3884 say(L"Testing universal variable parsing");
3885 const char *input =
3886 "# This file contains fish universal variable definitions.\n"
3887 "# VERSION: 3.0\n"
3888 "SETUVAR varA:ValA1\\x1eValA2\n"
3889 "SETUVAR --export varB:ValB1\n"
3890 "SETUVAR --nonsenseflag varC:ValC1\n"
3891 "SETUVAR --export --path varD:ValD1\n"
3892 "SETUVAR --path --path varE:ValE1\\x1eValE2\n";
3893
3894 const env_var_t::env_var_flags_t flag_export = env_var_t::flag_export;
3895 const env_var_t::env_var_flags_t flag_pathvar = env_var_t::flag_pathvar;
3896
3897 var_table_t vars;
3898 vars[L"varA"] = env_var_t(wcstring_list_t{L"ValA1", L"ValA2"}, 0);
3899 vars[L"varB"] = env_var_t(wcstring_list_t{L"ValB1"}, flag_export);
3900 vars[L"varC"] = env_var_t(wcstring_list_t{L"ValC1"}, 0);
3901 vars[L"varD"] = env_var_t(wcstring_list_t{L"ValD1"}, flag_export | flag_pathvar);
3902 vars[L"varE"] = env_var_t(wcstring_list_t{L"ValE1", L"ValE2"}, flag_pathvar);
3903
3904 var_table_t parsed_vars;
3905 env_universal_t::populate_variables(input, &parsed_vars);
3906 do_test(vars == parsed_vars);
3907 }
3908
test_universal_parsing_legacy()3909 static void test_universal_parsing_legacy() {
3910 say(L"Testing universal variable legacy parsing");
3911 const char *input =
3912 "# This file contains fish universal variable definitions.\n"
3913 "SET varA:ValA1\\x1eValA2\n"
3914 "SET_EXPORT varB:ValB1\n";
3915
3916 var_table_t vars;
3917 vars[L"varA"] = env_var_t(wcstring_list_t{L"ValA1", L"ValA2"}, 0);
3918 vars[L"varB"] = env_var_t(wcstring_list_t{L"ValB1"}, env_var_t::flag_export);
3919
3920 var_table_t parsed_vars;
3921 env_universal_t::populate_variables(input, &parsed_vars);
3922 do_test(vars == parsed_vars);
3923 }
3924
callback_data_less_than(const callback_data_t & a,const callback_data_t & b)3925 static bool callback_data_less_than(const callback_data_t &a, const callback_data_t &b) {
3926 return a.key < b.key;
3927 }
3928
test_universal_callbacks()3929 static void test_universal_callbacks() {
3930 say(L"Testing universal callbacks");
3931 if (system("mkdir -p test/fish_uvars_test/")) err(L"mkdir failed");
3932 callback_data_list_t callbacks;
3933 env_universal_t uvars1;
3934 env_universal_t uvars2;
3935 uvars1.initialize_at_path(callbacks, UVARS_TEST_PATH);
3936 uvars2.initialize_at_path(callbacks, UVARS_TEST_PATH);
3937
3938 env_var_t::env_var_flags_t noflags = 0;
3939
3940 // Put some variables into both.
3941 uvars1.set(L"alpha", env_var_t{L"1", noflags});
3942 uvars1.set(L"beta", env_var_t{L"1", noflags});
3943 uvars1.set(L"delta", env_var_t{L"1", noflags});
3944 uvars1.set(L"epsilon", env_var_t{L"1", noflags});
3945 uvars1.set(L"lambda", env_var_t{L"1", noflags});
3946 uvars1.set(L"kappa", env_var_t{L"1", noflags});
3947 uvars1.set(L"omicron", env_var_t{L"1", noflags});
3948
3949 uvars1.sync(callbacks);
3950 uvars2.sync(callbacks);
3951
3952 // Change uvars1.
3953 uvars1.set(L"alpha", env_var_t{L"2", noflags}); // changes value
3954 uvars1.set(L"beta", env_var_t{L"1", env_var_t::flag_export}); // changes export
3955 uvars1.remove(L"delta"); // erases value
3956 uvars1.set(L"epsilon", env_var_t{L"1", noflags}); // changes nothing
3957 uvars1.sync(callbacks);
3958
3959 // Change uvars2. It should treat its value as correct and ignore changes from uvars1.
3960 uvars2.set(L"lambda", {L"1", noflags}); // same value
3961 uvars2.set(L"kappa", {L"2", noflags}); // different value
3962
3963 // Now see what uvars2 sees.
3964 callbacks.clear();
3965 uvars2.sync(callbacks);
3966
3967 // Sort them to get them in a predictable order.
3968 std::sort(callbacks.begin(), callbacks.end(), callback_data_less_than);
3969
3970 // Should see exactly three changes.
3971 do_test(callbacks.size() == 3);
3972 do_test(callbacks.at(0).key == L"alpha");
3973 do_test(callbacks.at(0).val == wcstring{L"2"});
3974 do_test(callbacks.at(1).key == L"beta");
3975 do_test(callbacks.at(1).val == wcstring{L"1"});
3976 do_test(callbacks.at(2).key == L"delta");
3977 do_test(callbacks.at(2).val == none());
3978 system_assert("rm -Rf test/fish_uvars_test/");
3979 }
3980
test_universal_formats()3981 static void test_universal_formats() {
3982 say(L"Testing universal format detection");
3983 const struct {
3984 const char *str;
3985 uvar_format_t format;
3986 } tests[] = {
3987 {"# VERSION: 3.0", uvar_format_t::fish_3_0},
3988 {"# version: 3.0", uvar_format_t::fish_2_x},
3989 {"# blah blahVERSION: 3.0", uvar_format_t::fish_2_x},
3990 {"stuff\n# blah blahVERSION: 3.0", uvar_format_t::fish_2_x},
3991 {"# blah\n# VERSION: 3.0", uvar_format_t::fish_3_0},
3992 {"# blah\n#VERSION: 3.0", uvar_format_t::fish_3_0},
3993 {"# blah\n#VERSION:3.0", uvar_format_t::fish_3_0},
3994 {"# blah\n#VERSION:3.1", uvar_format_t::future},
3995 };
3996 for (const auto &test : tests) {
3997 uvar_format_t format = env_universal_t::format_for_contents(test.str);
3998 do_test(format == test.format);
3999 }
4000 }
4001
test_universal_ok_to_save()4002 static void test_universal_ok_to_save() {
4003 // Ensure we don't try to save after reading from a newer fish.
4004 say(L"Testing universal Ok to save");
4005 if (system("mkdir -p test/fish_uvars_test/")) err(L"mkdir failed");
4006 constexpr const char contents[] = "# VERSION: 99999.99\n";
4007 FILE *fp = fopen(wcs2string(UVARS_TEST_PATH).c_str(), "w");
4008 assert(fp && "Failed to open UVARS_TEST_PATH for writing");
4009 fwrite(contents, const_strlen(contents), 1, fp);
4010 fclose(fp);
4011
4012 file_id_t before_id = file_id_for_path(UVARS_TEST_PATH);
4013 do_test(before_id != kInvalidFileID && "UVARS_TEST_PATH should be readable");
4014
4015 callback_data_list_t cbs;
4016 env_universal_t uvars;
4017 uvars.initialize_at_path(cbs, UVARS_TEST_PATH);
4018 do_test(!uvars.is_ok_to_save() && "Should not be OK to save");
4019 uvars.sync(cbs);
4020 cbs.clear();
4021 do_test(!uvars.is_ok_to_save() && "Should still not be OK to save");
4022 uvars.set(L"SOMEVAR", env_var_t{wcstring{L"SOMEVALUE"}, 0});
4023 uvars.sync(cbs);
4024
4025 // Ensure file is same.
4026 file_id_t after_id = file_id_for_path(UVARS_TEST_PATH);
4027 do_test(before_id == after_id && "UVARS_TEST_PATH should not have changed");
4028 system_assert("rm -Rf test/fish_uvars_test/");
4029 }
4030
poll_notifier(const std::unique_ptr<universal_notifier_t> & note)4031 bool poll_notifier(const std::unique_ptr<universal_notifier_t> ¬e) {
4032 if (note->poll()) return true;
4033
4034 bool result = false;
4035 int fd = note->notification_fd();
4036 if (fd >= 0 && select_wrapper_t::poll_fd_readable(fd)) {
4037 result = note->notification_fd_became_readable(fd);
4038 }
4039 return result;
4040 }
4041
test_notifiers_with_strategy(universal_notifier_t::notifier_strategy_t strategy)4042 static void test_notifiers_with_strategy(universal_notifier_t::notifier_strategy_t strategy) {
4043 say(L"Testing universal notifiers with strategy %d", (int)strategy);
4044 constexpr size_t notifier_count = 16;
4045 std::unique_ptr<universal_notifier_t> notifiers[notifier_count];
4046
4047 // Populate array of notifiers.
4048 for (size_t i = 0; i < notifier_count; i++) {
4049 notifiers[i] = universal_notifier_t::new_notifier_for_strategy(strategy, UVARS_TEST_PATH);
4050 }
4051
4052 // Nobody should poll yet.
4053 for (size_t i = 0; i < notifier_count; i++) {
4054 if (poll_notifier(notifiers[i])) {
4055 err(L"Universal variable notifier polled true before any changes, with strategy %d",
4056 (int)strategy);
4057 }
4058 }
4059
4060 // Tweak each notifier. Verify that others see it.
4061 for (size_t post_idx = 0; post_idx < notifier_count; post_idx++) {
4062 notifiers[post_idx]->post_notification();
4063
4064 if (strategy == universal_notifier_t::strategy_notifyd) {
4065 // notifyd requires a round trip to the notifyd server, which means we have to wait a
4066 // little bit to receive it. In practice 40 ms seems to be enough.
4067 usleep(40000);
4068 }
4069
4070 for (size_t i = 0; i < notifier_count; i++) {
4071 bool polled = poll_notifier(notifiers[i]);
4072
4073 // We aren't concerned with the one who posted. Poll from it (to drain it), and then
4074 // skip it.
4075 if (i == post_idx) {
4076 continue;
4077 }
4078
4079 if (!polled) {
4080 err(L"Universal variable notifier (%lu) %p polled failed to notice changes, with "
4081 L"strategy %d",
4082 i, notifiers[i].get(), (int)strategy);
4083 continue;
4084 }
4085 // It should not poll again immediately.
4086 if (poll_notifier(notifiers[i])) {
4087 err(L"Universal variable notifier (%lu) %p polled twice in a row with strategy %d",
4088 i, notifiers[i].get(), (int)strategy);
4089 }
4090 }
4091
4092 // Named pipes have special cleanup requirements.
4093 if (strategy == universal_notifier_t::strategy_named_pipe) {
4094 usleep(1000000 / 10); // corresponds to NAMED_PIPE_FLASH_DURATION_USEC
4095 // Have to clean up the posted one first, so that the others see the pipe become no
4096 // longer readable.
4097 poll_notifier(notifiers[post_idx]);
4098 for (size_t i = 0; i < notifier_count; i++) {
4099 poll_notifier(notifiers[i]);
4100 }
4101 }
4102 }
4103
4104 // Nobody should poll now.
4105 for (size_t i = 0; i < notifier_count; i++) {
4106 if (poll_notifier(notifiers[i])) {
4107 err(L"Universal variable notifier polled true after all changes, with strategy %d",
4108 (int)strategy);
4109 }
4110 }
4111 }
4112
test_universal_notifiers()4113 static void test_universal_notifiers() {
4114 if (system("mkdir -p test/fish_uvars_test/ && touch test/fish_uvars_test/varsfile.txt")) {
4115 err(L"mkdir failed");
4116 }
4117
4118 auto strategy = universal_notifier_t::resolve_default_strategy();
4119 test_notifiers_with_strategy(strategy);
4120 }
4121
4122 class history_tests_t {
4123 public:
4124 static void test_history();
4125 static void test_history_merge();
4126 static void test_history_path_detection();
4127 static void test_history_formats();
4128 // static void test_history_speed(void);
4129 static void test_history_races();
4130 static void test_history_races_pound_on_history(size_t item_count, size_t idx);
4131 };
4132
random_string()4133 static wcstring random_string() {
4134 wcstring result;
4135 size_t max = 1 + random() % 32;
4136 while (max--) {
4137 wchar_t c = 1 + random() % ESCAPE_TEST_CHAR;
4138 result.push_back(c);
4139 }
4140 return result;
4141 }
4142
test_history()4143 void history_tests_t::test_history() {
4144 history_search_t searcher;
4145 say(L"Testing history");
4146
4147 const wcstring_list_t items = {L"Gamma", L"beta", L"BetA", L"Beta", L"alpha",
4148 L"AlphA", L"Alpha", L"alph", L"ALPH", L"ZZZ"};
4149 const history_search_flags_t nocase = history_search_ignore_case;
4150
4151 // Populate a history.
4152 std::shared_ptr<history_t> history = history_t::with_name(L"test_history");
4153 history->clear();
4154 for (const wcstring &s : items) {
4155 history->add(s);
4156 }
4157
4158 // Helper to set expected items to those matching a predicate, in reverse order.
4159 wcstring_list_t expected;
4160 auto set_expected = [&](const std::function<bool(const wcstring &)> &filt) {
4161 expected.clear();
4162 for (const auto &s : items) {
4163 if (filt(s)) expected.push_back(s);
4164 }
4165 std::reverse(expected.begin(), expected.end());
4166 };
4167
4168 // Items matching "a", case-sensitive.
4169 searcher = history_search_t(history, L"a");
4170 set_expected([](const wcstring &s) { return s.find(L'a') != wcstring::npos; });
4171 test_history_matches(searcher, expected, __LINE__);
4172
4173 // Items matching "alpha", case-insensitive.
4174 searcher = history_search_t(history, L"AlPhA", history_search_type_t::contains, nocase);
4175 set_expected([](const wcstring &s) { return wcstolower(s).find(L"alpha") != wcstring::npos; });
4176 test_history_matches(searcher, expected, __LINE__);
4177
4178 // Items matching "et", case-sensitive.
4179 searcher = history_search_t(history, L"et");
4180 set_expected([](const wcstring &s) { return s.find(L"et") != wcstring::npos; });
4181 test_history_matches(searcher, expected, __LINE__);
4182
4183 // Items starting with "be", case-sensitive.
4184 searcher = history_search_t(history, L"be", history_search_type_t::prefix, 0);
4185 set_expected([](const wcstring &s) { return string_prefixes_string(L"be", s); });
4186 test_history_matches(searcher, expected, __LINE__);
4187
4188 // Items starting with "be", case-insensitive.
4189 searcher = history_search_t(history, L"be", history_search_type_t::prefix, nocase);
4190 set_expected(
4191 [](const wcstring &s) { return string_prefixes_string_case_insensitive(L"be", s); });
4192 test_history_matches(searcher, expected, __LINE__);
4193
4194 // Items exactly matching "alph", case-sensitive.
4195 searcher = history_search_t(history, L"alph", history_search_type_t::exact, 0);
4196 set_expected([](const wcstring &s) { return s == L"alph"; });
4197 test_history_matches(searcher, expected, __LINE__);
4198
4199 // Items exactly matching "alph", case-insensitive.
4200 searcher = history_search_t(history, L"alph", history_search_type_t::exact, nocase);
4201 set_expected([](const wcstring &s) { return wcstolower(s) == L"alph"; });
4202 test_history_matches(searcher, expected, __LINE__);
4203
4204 // Test item removal case-sensitive.
4205 searcher = history_search_t(history, L"Alpha");
4206 test_history_matches(searcher, {L"Alpha"}, __LINE__);
4207 history->remove(L"Alpha");
4208 searcher = history_search_t(history, L"Alpha");
4209 test_history_matches(searcher, {}, __LINE__);
4210
4211 // Test history escaping and unescaping, yaml, etc.
4212 history_item_list_t before, after;
4213 history->clear();
4214 size_t i, max = 100;
4215 for (i = 1; i <= max; i++) {
4216 // Generate a value.
4217 wcstring value = wcstring(L"test item ") + to_string(i);
4218
4219 // Maybe add some backslashes.
4220 if (i % 3 == 0) value.append(L"(slashies \\\\\\ slashies)");
4221
4222 // Generate some paths.
4223 path_list_t paths;
4224 size_t count = random() % 6;
4225 while (count--) {
4226 paths.push_back(random_string());
4227 }
4228
4229 // Record this item.
4230 history_item_t item(value, time(NULL));
4231 item.required_paths = paths;
4232 before.push_back(item);
4233 history->add(std::move(item));
4234 }
4235 history->save();
4236
4237 // Empty items should just be dropped (#6032).
4238 history->add(L"");
4239 do_test(!history->item_at_index(1).contents.empty());
4240
4241 // Read items back in reverse order and ensure they're the same.
4242 for (i = 100; i >= 1; i--) {
4243 history_item_t item = history->item_at_index(i);
4244 do_test(!item.empty());
4245 after.push_back(item);
4246 }
4247 do_test(before.size() == after.size());
4248 for (size_t i = 0; i < before.size(); i++) {
4249 const history_item_t &bef = before.at(i), &aft = after.at(i);
4250 do_test(bef.contents == aft.contents);
4251 do_test(bef.creation_timestamp == aft.creation_timestamp);
4252 do_test(bef.required_paths == aft.required_paths);
4253 }
4254
4255 // Clean up after our tests.
4256 history->clear();
4257 }
4258 // Wait until the next second.
time_barrier()4259 static void time_barrier() {
4260 time_t start = time(NULL);
4261 do {
4262 usleep(1000);
4263 } while (time(NULL) == start);
4264 }
4265
generate_history_lines(size_t item_count,size_t idx)4266 static wcstring_list_t generate_history_lines(size_t item_count, size_t idx) {
4267 wcstring_list_t result;
4268 result.reserve(item_count);
4269 for (unsigned long i = 0; i < item_count; i++) {
4270 result.push_back(format_string(L"%ld %lu", (unsigned long)idx, (unsigned long)i));
4271 }
4272 return result;
4273 }
4274
test_history_races_pound_on_history(size_t item_count,size_t idx)4275 void history_tests_t::test_history_races_pound_on_history(size_t item_count, size_t idx) {
4276 // Called in child thread to modify history.
4277 history_t hist(L"race_test");
4278 const wcstring_list_t hist_lines = generate_history_lines(item_count, idx);
4279 for (const wcstring &line : hist_lines) {
4280 hist.add(line);
4281 hist.save();
4282 }
4283 }
4284
test_history_races()4285 void history_tests_t::test_history_races() {
4286 say(L"Testing history race conditions");
4287
4288 // It appears TSAN and ASAN's allocators do not release their locks properly in atfork, so
4289 // allocating with multiple threads risks deadlock. Drain threads before running under ASAN.
4290 // TODO: stop forking with these tests.
4291 bool needs_thread_drain = false;
4292 #if __SANITIZE_ADDRESS__
4293 needs_thread_drain |= true;
4294 #endif
4295 #if defined(__has_feature)
4296 needs_thread_drain |= __has_feature(thread_sanitizer) || __has_feature(address_sanitizer);
4297 #endif
4298
4299 if (needs_thread_drain) {
4300 iothread_drain_all();
4301 }
4302
4303 // Test concurrent history writing.
4304 // How many concurrent writers we have
4305 constexpr size_t RACE_COUNT = 4;
4306
4307 // How many items each writer makes
4308 constexpr size_t ITEM_COUNT = 256;
4309
4310 // Ensure history is clear.
4311 history_t(L"race_test").clear();
4312
4313 // hist.chaos_mode = true;
4314
4315 std::thread children[RACE_COUNT];
4316 for (size_t i = 0; i < RACE_COUNT; i++) {
4317 children[i] = std::thread([=] { test_history_races_pound_on_history(ITEM_COUNT, i); });
4318 }
4319
4320 // Wait for all children.
4321 for (std::thread &child : children) {
4322 child.join();
4323 }
4324
4325 // Compute the expected lines.
4326 std::array<wcstring_list_t, RACE_COUNT> expected_lines;
4327 for (size_t i = 0; i < RACE_COUNT; i++) {
4328 expected_lines[i] = generate_history_lines(ITEM_COUNT, i);
4329 }
4330
4331 // Ensure we consider the lines that have been outputted as part of our history.
4332 time_barrier();
4333
4334 // Ensure that we got sane, sorted results.
4335 history_t hist(L"race_test");
4336 hist.chaos_mode = !true;
4337
4338 // History is enumerated from most recent to least
4339 // Every item should be the last item in some array
4340 size_t hist_idx;
4341 for (hist_idx = 1;; hist_idx++) {
4342 history_item_t item = hist.item_at_index(hist_idx);
4343 if (item.empty()) break;
4344
4345 bool found = false;
4346 for (wcstring_list_t &list : expected_lines) {
4347 auto iter = std::find(list.begin(), list.end(), item.contents);
4348 if (iter != list.end()) {
4349 found = true;
4350
4351 // Remove everything from this item on
4352 auto cursor = list.end();
4353 if (cursor + 1 != list.end()) {
4354 while (--cursor != iter) {
4355 err(L"Item dropped from history: %ls", cursor->c_str());
4356 }
4357 }
4358 list.erase(iter, list.end());
4359 break;
4360 }
4361 }
4362 if (!found) {
4363 err(L"Line '%ls' found in history, but not found in some array", item.str().c_str());
4364 for (wcstring_list_t &list : expected_lines) {
4365 if (!list.empty()) {
4366 fprintf(stderr, "\tRemaining: %ls\n", list.back().c_str());
4367 }
4368 }
4369 }
4370 }
4371
4372 // +1 to account for history's 1-based offset
4373 size_t expected_idx = RACE_COUNT * ITEM_COUNT + 1;
4374 if (hist_idx != expected_idx) {
4375 err(L"Expected %lu items, but instead got %lu items", expected_idx, hist_idx);
4376 }
4377
4378 // See if anything is left in the arrays
4379 for (const wcstring_list_t &list : expected_lines) {
4380 for (const wcstring &str : list) {
4381 err(L"Line '%ls' still left in the array", str.c_str());
4382 }
4383 }
4384 hist.clear();
4385 }
4386
test_history_merge()4387 void history_tests_t::test_history_merge() {
4388 // In a single fish process, only one history is allowed to exist with the given name But it's
4389 // common to have multiple history instances with the same name active in different processes,
4390 // e.g. when you have multiple shells open. We try to get that right and merge all their history
4391 // together. Test that case.
4392 say(L"Testing history merge");
4393 const size_t count = 3;
4394 const wcstring name = L"merge_test";
4395 std::shared_ptr<history_t> hists[count] = {std::make_shared<history_t>(name),
4396 std::make_shared<history_t>(name),
4397 std::make_shared<history_t>(name)};
4398 const wcstring texts[count] = {L"History 1", L"History 2", L"History 3"};
4399 const wcstring alt_texts[count] = {L"History Alt 1", L"History Alt 2", L"History Alt 3"};
4400
4401 // Make sure history is clear.
4402 for (auto &hist : hists) {
4403 hist->clear();
4404 }
4405
4406 // Make sure we don't add an item in the same second as we created the history.
4407 time_barrier();
4408
4409 // Add a different item to each.
4410 for (size_t i = 0; i < count; i++) {
4411 hists[i]->add(texts[i]);
4412 }
4413
4414 // Save them.
4415 for (auto &hist : hists) {
4416 hist->save();
4417 }
4418
4419 // Make sure each history contains what it ought to, but they have not leaked into each other.
4420 for (size_t i = 0; i < count; i++) {
4421 for (size_t j = 0; j < count; j++) {
4422 bool does_contain = history_contains(hists[i], texts[j]);
4423 bool should_contain = (i == j);
4424 do_test(should_contain == does_contain);
4425 }
4426 }
4427
4428 // Make a new history. It should contain everything. The time_barrier() is so that the timestamp
4429 // is newer, since we only pick up items whose timestamp is before the birth stamp.
4430 time_barrier();
4431 std::shared_ptr<history_t> everything = std::make_shared<history_t>(name);
4432 for (const auto &text : texts) {
4433 do_test(history_contains(everything, text));
4434 }
4435
4436 // Tell all histories to merge. Now everybody should have everything.
4437 for (auto &hist : hists) {
4438 hist->incorporate_external_changes();
4439 }
4440
4441 // Everyone should also have items in the same order (#2312)
4442 wcstring_list_t hist_vals1;
4443 hists[0]->get_history(hist_vals1);
4444 for (const auto &hist : hists) {
4445 wcstring_list_t hist_vals2;
4446 hist->get_history(hist_vals2);
4447 do_test(hist_vals1 == hist_vals2);
4448 }
4449
4450 // Add some more per-history items.
4451 for (size_t i = 0; i < count; i++) {
4452 hists[i]->add(alt_texts[i]);
4453 }
4454 // Everybody should have old items, but only one history should have each new item.
4455 for (size_t i = 0; i < count; i++) {
4456 for (size_t j = 0; j < count; j++) {
4457 // Old item.
4458 do_test(history_contains(hists[i], texts[j]));
4459
4460 // New item.
4461 bool does_contain = history_contains(hists[i], alt_texts[j]);
4462 bool should_contain = (i == j);
4463 do_test(should_contain == does_contain);
4464 }
4465 }
4466
4467 // Make sure incorporate_external_changes doesn't drop items! (#3496)
4468 history_t *const writer = hists[0].get();
4469 history_t *const reader = hists[1].get();
4470 const wcstring more_texts[] = {L"Item_#3496_1", L"Item_#3496_2", L"Item_#3496_3",
4471 L"Item_#3496_4", L"Item_#3496_5", L"Item_#3496_6"};
4472 for (size_t i = 0; i < sizeof more_texts / sizeof *more_texts; i++) {
4473 // time_barrier because merging will ignore items that may be newer
4474 if (i > 0) time_barrier();
4475 writer->add(more_texts[i]);
4476 writer->incorporate_external_changes();
4477 reader->incorporate_external_changes();
4478 for (size_t j = 0; j < i; j++) {
4479 do_test(history_contains(reader, more_texts[j]));
4480 }
4481 }
4482 everything->clear();
4483 }
4484
test_history_path_detection()4485 void history_tests_t::test_history_path_detection() {
4486 // Regression test for #7582.
4487 say(L"Testing history path detection");
4488 char tmpdirbuff[] = "/tmp/fish_test_history.XXXXXX";
4489 wcstring tmpdir = str2wcstring(mkdtemp(tmpdirbuff));
4490 if (!string_suffixes_string(L"/", tmpdir)) {
4491 tmpdir.push_back(L'/');
4492 }
4493
4494 // Place one valid file in the directory.
4495 wcstring filename = L"testfile";
4496 std::string path = wcs2string(tmpdir + filename);
4497 FILE *f = fopen(path.c_str(), "w");
4498 if (!f) {
4499 err(L"Failed to open test file from history path detection");
4500 return;
4501 }
4502 fclose(f);
4503
4504 std::shared_ptr<test_environment_t> vars = std::make_shared<test_environment_t>();
4505 vars->vars[L"PWD"] = tmpdir;
4506 vars->vars[L"HOME"] = tmpdir;
4507
4508 std::shared_ptr<history_t> history = history_t::with_name(L"path_detection");
4509 history_t::add_pending_with_file_detection(history, L"cmd0 not/a/valid/path", vars);
4510 history_t::add_pending_with_file_detection(history, L"cmd1 " + filename, vars);
4511 history_t::add_pending_with_file_detection(history, L"cmd2 " + tmpdir + L"/" + filename, vars);
4512 history_t::add_pending_with_file_detection(history, L"cmd3 $HOME/" + filename, vars);
4513 history_t::add_pending_with_file_detection(history, L"cmd4 $HOME/notafile", vars);
4514 history_t::add_pending_with_file_detection(history, L"cmd5 ~/" + filename, vars);
4515 history_t::add_pending_with_file_detection(history, L"cmd6 ~/notafile", vars);
4516 history_t::add_pending_with_file_detection(history, L"cmd7 ~/*f*", vars);
4517 history_t::add_pending_with_file_detection(history, L"cmd8 ~/*zzz*", vars);
4518 history->resolve_pending();
4519
4520 constexpr size_t hist_size = 9;
4521 if (history->size() != hist_size) {
4522 err(L"history has wrong size: %lu but expected %lu", (unsigned long)history->size(),
4523 (unsigned long)hist_size);
4524 history->clear();
4525 return;
4526 }
4527
4528 // Expected sets of paths.
4529 wcstring_list_t expected[hist_size] = {
4530 {}, // cmd0
4531 {filename}, // cmd1
4532 {tmpdir + L"/" + filename}, // cmd2
4533 {L"$HOME/" + filename}, // cmd3
4534 {}, // cmd4
4535 {L"~/" + filename}, // cmd5
4536 {}, // cmd6
4537 {}, // cmd7 - we do not expand globs
4538 {}, // cmd8
4539 };
4540
4541 size_t lap;
4542 const size_t maxlap = 128;
4543 for (lap = 0; lap < maxlap; lap++) {
4544 int failures = 0;
4545 bool last = (lap + 1 == maxlap);
4546 for (size_t i = 1; i <= hist_size; i++) {
4547 if (history->item_at_index(i).required_paths != expected[hist_size - i]) {
4548 failures += 1;
4549 if (last) {
4550 err(L"Wrong detected paths for item %lu", (unsigned long)i);
4551 }
4552 }
4553 }
4554 if (failures == 0) {
4555 break;
4556 }
4557 // The file detection takes a little time since it occurs in the background.
4558 // Loop until the test passes.
4559 usleep(1E6 / 500); // 1 msec
4560 }
4561 // fprintf(stderr, "History saving took %lu laps\n", (unsigned long)lap);
4562 history->clear();
4563 }
4564
install_sample_history(const wchar_t * name)4565 static bool install_sample_history(const wchar_t *name) {
4566 wcstring path;
4567 if (!path_get_data(path)) {
4568 err(L"Failed to get data directory");
4569 return false;
4570 }
4571 char command[512];
4572 snprintf(command, sizeof command, "cp tests/%ls %ls/%ls_history", name, path.c_str(), name);
4573 if (system(command)) {
4574 err(L"Failed to copy sample history");
4575 return false;
4576 }
4577 return true;
4578 }
4579
4580 /// Indicates whether the history is equal to the given null-terminated array of strings.
history_equals(const shared_ptr<history_t> & hist,const wchar_t * const * strings)4581 static bool history_equals(const shared_ptr<history_t> &hist, const wchar_t *const *strings) {
4582 // Count our expected items.
4583 size_t expected_count = 0;
4584 while (strings[expected_count]) {
4585 expected_count++;
4586 }
4587
4588 // Ensure the contents are the same.
4589 size_t history_idx = 1;
4590 size_t array_idx = 0;
4591 for (;;) {
4592 const wchar_t *expected = strings[array_idx];
4593 history_item_t item = hist->item_at_index(history_idx);
4594 if (expected == NULL) {
4595 if (!item.empty()) {
4596 err(L"Expected empty item at history index %lu, instead found: %ls", history_idx,
4597 item.str().c_str());
4598 }
4599 break;
4600 } else {
4601 if (item.str() != expected) {
4602 err(L"Expected '%ls', found '%ls' at index %lu", expected, item.str().c_str(),
4603 history_idx);
4604 }
4605 }
4606 history_idx++;
4607 array_idx++;
4608 }
4609
4610 return true;
4611 }
4612
test_history_formats()4613 void history_tests_t::test_history_formats() {
4614 const wchar_t *name;
4615
4616 // Test inferring and reading legacy and bash history formats.
4617 name = L"history_sample_fish_1_x";
4618 say(L"Testing %ls", name);
4619 if (!install_sample_history(name)) {
4620 err(L"Couldn't open file tests/%ls", name);
4621 } else {
4622 // Note: This is backwards from what appears in the file.
4623 const wchar_t *const expected[] = {
4624 L"#def", L"echo #abc", L"function yay\necho hi\nend", L"cd foobar", L"ls /", NULL};
4625
4626 auto test_history = history_t::with_name(name);
4627 if (!history_equals(test_history, expected)) {
4628 err(L"test_history_formats failed for %ls\n", name);
4629 }
4630 test_history->clear();
4631 }
4632
4633 name = L"history_sample_fish_2_0";
4634 say(L"Testing %ls", name);
4635 if (!install_sample_history(name)) {
4636 err(L"Couldn't open file tests/%ls", name);
4637 } else {
4638 const wchar_t *const expected[] = {L"echo this has\\\nbackslashes",
4639 L"function foo\necho bar\nend", L"echo alpha", NULL};
4640
4641 auto test_history = history_t::with_name(name);
4642 if (!history_equals(test_history, expected)) {
4643 err(L"test_history_formats failed for %ls\n", name);
4644 }
4645 test_history->clear();
4646 }
4647
4648 say(L"Testing bash import");
4649 FILE *f = fopen("tests/history_sample_bash", "r");
4650 if (!f) {
4651 err(L"Couldn't open file tests/history_sample_bash");
4652 } else {
4653 // The results are in the reverse order that they appear in the bash history file.
4654 // We don't expect whitespace to be elided (#4908: except for leading/trailing whitespace)
4655 const wchar_t *expected[] = {L"EOF",
4656 L"sleep 123",
4657 L"a && echo valid construct",
4658 L"final line",
4659 L"echo supsup",
4660 L"export XVAR='exported'",
4661 L"history --help",
4662 L"echo foo",
4663 NULL};
4664 auto test_history = history_t::with_name(L"bash_import");
4665 test_history->populate_from_bash(f);
4666 if (!history_equals(test_history, expected)) {
4667 err(L"test_history_formats failed for bash import\n");
4668 }
4669 test_history->clear();
4670 fclose(f);
4671 }
4672
4673 name = L"history_sample_corrupt1";
4674 say(L"Testing %ls", name);
4675 if (!install_sample_history(name)) {
4676 err(L"Couldn't open file tests/%ls", name);
4677 } else {
4678 // We simply invoke get_string_representation. If we don't die, the test is a success.
4679 auto test_history = history_t::with_name(name);
4680 const wchar_t *expected[] = {L"no_newline_at_end_of_file", L"corrupt_prefix",
4681 L"this_command_is_ok", NULL};
4682 if (!history_equals(test_history, expected)) {
4683 err(L"test_history_formats failed for %ls\n", name);
4684 }
4685 test_history->clear();
4686 }
4687 }
4688
4689 #if 0
4690 // This test isn't run at this time. It was added by commit b9283d48 but not actually enabled.
4691 void history_tests_t::test_history_speed(void)
4692 {
4693 say(L"Testing history speed (pid is %d)", getpid());
4694 std::unique_ptr<history_t> hist = make_unique<history_t>(L"speed_test");
4695 wcstring item = L"History Speed Test - X";
4696
4697 // Test for 10 seconds.
4698 double start = timef();
4699 double end = start + 10;
4700 double stop = 0;
4701 size_t count = 0;
4702 for (;;)
4703 {
4704 item[item.size() - 1] = L'0' + (count % 10);
4705 hist->add(item);
4706 count++;
4707
4708 stop = timef();
4709 if (stop >= end)
4710 break;
4711 }
4712 std::fwprintf(stdout, L"%lu items - %.2f msec per item\n", (unsigned long)count,
4713 (stop - start) * 1E6 / count);
4714 hist->clear();
4715 }
4716 #endif
4717
test_new_parser_correctness()4718 static void test_new_parser_correctness() {
4719 say(L"Testing parser correctness");
4720 const struct parser_test_t {
4721 const wchar_t *src;
4722 bool ok;
4723 } parser_tests[] = {
4724 {L"; ; ; ", true},
4725 {L"if ; end", false},
4726 {L"if true ; end", true},
4727 {L"if true; end ; end", false},
4728 {L"if end; end ; end", false},
4729 {L"if end", false},
4730 {L"end", false},
4731 {L"for i i", false},
4732 {L"for i in a b c ; end", true},
4733 {L"begin end", true},
4734 {L"begin; end", true},
4735 {L"begin if true; end; end;", true},
4736 {L"begin if true ; echo hi ; end; end", true},
4737 {L"true && false || false", true},
4738 {L"true || false; and true", true},
4739 {L"true || ||", false},
4740 {L"|| true", false},
4741 {L"true || \n\n false", true},
4742 };
4743
4744 for (const auto &test : parser_tests) {
4745 auto ast = ast::ast_t::parse(test.src);
4746 bool success = !ast.errored();
4747 if (success && !test.ok) {
4748 err(L"\"%ls\" should NOT have parsed, but did", test.src);
4749 } else if (!success && test.ok) {
4750 err(L"\"%ls\" should have parsed, but failed", test.src);
4751 }
4752 }
4753 say(L"Parse tests complete");
4754 }
4755
4756 // Given that we have an array of 'fuzz_count' strings, we wish to enumerate all permutations of
4757 // 'len' values. We do this by incrementing an integer, interpreting it as "base fuzz_count".
string_for_permutation(const wcstring * fuzzes,size_t fuzz_count,size_t len,size_t permutation,wcstring * out_str)4758 static inline bool string_for_permutation(const wcstring *fuzzes, size_t fuzz_count, size_t len,
4759 size_t permutation, wcstring *out_str) {
4760 out_str->clear();
4761
4762 size_t remaining_permutation = permutation;
4763 for (size_t i = 0; i < len; i++) {
4764 size_t idx = remaining_permutation % fuzz_count;
4765 remaining_permutation /= fuzz_count;
4766
4767 out_str->append(fuzzes[idx]);
4768 out_str->push_back(L' ');
4769 }
4770 // Return false if we wrapped.
4771 return remaining_permutation == 0;
4772 }
4773
test_new_parser_fuzzing()4774 static void test_new_parser_fuzzing() {
4775 say(L"Fuzzing parser");
4776 const wcstring fuzzes[] = {
4777 L"if", L"else", L"for", L"in", L"while", L"begin", L"function",
4778 L"switch", L"case", L"end", L"and", L"or", L"not", L"command",
4779 L"builtin", L"foo", L"|", L"^", L"&", L";",
4780 };
4781
4782 // Generate a list of strings of all keyword / token combinations.
4783 wcstring src;
4784 src.reserve(128);
4785
4786 parse_error_list_t errors;
4787
4788 double start = timef();
4789 bool log_it = true;
4790 unsigned long max_len = 5;
4791 for (unsigned long len = 0; len < max_len; len++) {
4792 if (log_it) std::fwprintf(stderr, L"%lu / %lu...", len, max_len);
4793
4794 // We wish to look at all permutations of 4 elements of 'fuzzes' (with replacement).
4795 // Construct an int and keep incrementing it.
4796 unsigned long permutation = 0;
4797 while (string_for_permutation(fuzzes, sizeof fuzzes / sizeof *fuzzes, len, permutation++,
4798 &src)) {
4799 ast::ast_t::parse(src);
4800 }
4801 if (log_it) std::fwprintf(stderr, L"done (%lu)\n", permutation);
4802 }
4803 double end = timef();
4804 if (log_it) say(L"All fuzzed in %f seconds!", end - start);
4805 }
4806
4807 // Parse a statement, returning the command, args (joined by spaces), and the decoration. Returns
4808 // true if successful.
test_1_parse_ll2(const wcstring & src,wcstring * out_cmd,wcstring * out_joined_args,enum statement_decoration_t * out_deco)4809 static bool test_1_parse_ll2(const wcstring &src, wcstring *out_cmd, wcstring *out_joined_args,
4810 enum statement_decoration_t *out_deco) {
4811 using namespace ast;
4812 out_cmd->clear();
4813 out_joined_args->clear();
4814 *out_deco = statement_decoration_t::none;
4815
4816 auto ast = ast_t::parse(src);
4817 if (ast.errored()) return false;
4818
4819 // Get the statement. Should only have one.
4820 const decorated_statement_t *statement = nullptr;
4821 for (const auto &n : ast) {
4822 if (const auto *tmp = n.try_as<decorated_statement_t>()) {
4823 if (statement) {
4824 say(L"More than one decorated statement found in '%ls'", src.c_str());
4825 return false;
4826 }
4827 statement = tmp;
4828 }
4829 }
4830 if (!statement) {
4831 say(L"No decorated statement found in '%ls'", src.c_str());
4832 return false;
4833 }
4834
4835 // Return its decoration and command.
4836 *out_deco = statement->decoration();
4837 *out_cmd = statement->command.source(src);
4838
4839 // Return arguments separated by spaces.
4840 bool first = true;
4841 for (const ast::argument_or_redirection_t &arg : statement->args_or_redirs) {
4842 if (!arg.is_argument()) continue;
4843 if (!first) out_joined_args->push_back(L' ');
4844 out_joined_args->append(arg.source(src));
4845 first = false;
4846 }
4847
4848 return true;
4849 }
4850
4851 // Verify that 'function -h' and 'function --help' are plain statements but 'function --foo' is
4852 // not (issue #1240).
4853 template <ast::type_t Type>
check_function_help(const wchar_t * src)4854 static void check_function_help(const wchar_t *src) {
4855 using namespace ast;
4856 auto ast = ast_t::parse(src);
4857 if (ast.errored()) {
4858 err(L"Failed to parse '%ls'", src);
4859 }
4860
4861 int count = 0;
4862 for (const node_t &node : ast) {
4863 count += (node.type == Type);
4864 }
4865 if (count == 0) {
4866 err(L"Failed to find node of type '%ls'", ast_type_to_string(Type));
4867 } else if (count > 1) {
4868 err(L"Found too many nodes of type '%ls'", ast_type_to_string(Type));
4869 }
4870 }
4871
4872 // Test the LL2 (two token lookahead) nature of the parser by exercising the special builtin and
4873 // command handling. In particular, 'command foo' should be a decorated statement 'foo' but 'command
4874 // -help' should be an undecorated statement 'command' with argument '--help', and NOT attempt to
4875 // run a command called '--help'.
test_new_parser_ll2()4876 static void test_new_parser_ll2() {
4877 say(L"Testing parser two-token lookahead");
4878
4879 const struct {
4880 wcstring src;
4881 wcstring cmd;
4882 wcstring args;
4883 enum statement_decoration_t deco;
4884 } tests[] = {{L"echo hello", L"echo", L"hello", statement_decoration_t::none},
4885 {L"command echo hello", L"echo", L"hello", statement_decoration_t::command},
4886 {L"exec echo hello", L"echo", L"hello", statement_decoration_t::exec},
4887 {L"command command hello", L"command", L"hello", statement_decoration_t::command},
4888 {L"builtin command hello", L"command", L"hello", statement_decoration_t::builtin},
4889 {L"command --help", L"command", L"--help", statement_decoration_t::none},
4890 {L"command -h", L"command", L"-h", statement_decoration_t::none},
4891 {L"command", L"command", L"", statement_decoration_t::none},
4892 {L"command -", L"command", L"-", statement_decoration_t::none},
4893 {L"command --", L"command", L"--", statement_decoration_t::none},
4894 {L"builtin --names", L"builtin", L"--names", statement_decoration_t::none},
4895 {L"function", L"function", L"", statement_decoration_t::none},
4896 {L"function --help", L"function", L"--help", statement_decoration_t::none}};
4897
4898 for (const auto &test : tests) {
4899 wcstring cmd, args;
4900 enum statement_decoration_t deco = statement_decoration_t::none;
4901 bool success = test_1_parse_ll2(test.src, &cmd, &args, &deco);
4902 if (!success) err(L"Parse of '%ls' failed on line %ld", test.cmd.c_str(), (long)__LINE__);
4903 if (cmd != test.cmd)
4904 err(L"When parsing '%ls', expected command '%ls' but got '%ls' on line %ld",
4905 test.src.c_str(), test.cmd.c_str(), cmd.c_str(), (long)__LINE__);
4906 if (args != test.args)
4907 err(L"When parsing '%ls', expected args '%ls' but got '%ls' on line %ld",
4908 test.src.c_str(), test.args.c_str(), args.c_str(), (long)__LINE__);
4909 if (deco != test.deco)
4910 err(L"When parsing '%ls', expected decoration %d but got %d on line %ld",
4911 test.src.c_str(), (int)test.deco, (int)deco, (long)__LINE__);
4912 }
4913
4914 check_function_help<ast::type_t::decorated_statement>(L"function -h");
4915 check_function_help<ast::type_t::decorated_statement>(L"function --help");
4916 check_function_help<ast::type_t::function_header>(L"function --foo; end");
4917 check_function_help<ast::type_t::function_header>(L"function foo; end");
4918 }
4919
test_new_parser_ad_hoc()4920 static void test_new_parser_ad_hoc() {
4921 using namespace ast;
4922 // Very ad-hoc tests for issues encountered.
4923 say(L"Testing new parser ad hoc tests");
4924
4925 // Ensure that 'case' terminates a job list.
4926 const wcstring src = L"switch foo ; case bar; case baz; end";
4927 auto ast = ast_t::parse(src);
4928 if (ast.errored()) {
4929 err(L"Parsing failed");
4930 }
4931
4932 // Expect two case_item_lists. The bug was that we'd
4933 // try to run a command 'case'.
4934 int count = 0;
4935 for (const auto &n : ast) {
4936 count += (n.type == type_t::case_item);
4937 }
4938 if (count != 2) {
4939 err(L"Expected 2 case item nodes, found %d", count);
4940 }
4941
4942 // Ensure that naked variable assignments don't hang.
4943 // The bug was that "a=" would produce an error but not be consumed,
4944 // leading to an infinite loop.
4945
4946 // By itself it should produce an error.
4947 ast = ast_t::parse(L"a=");
4948 do_test(ast.errored());
4949
4950 // If we are leaving things unterminated, this should not produce an error.
4951 // i.e. when typing "a=" at the command line, it should be treated as valid
4952 // because we don't want to color it as an error.
4953 ast = ast_t::parse(L"a=", parse_flag_leave_unterminated);
4954 do_test(!ast.errored());
4955
4956 parse_error_list_t errors;
4957 ast = ast_t::parse(L"begin; echo (", parse_flag_leave_unterminated, &errors);
4958 do_test(errors.size() == 1 && errors.at(0).code == parse_error_tokenizer_unterminated_subshell);
4959
4960 errors.clear();
4961 ast = ast_t::parse(L"for x in (", parse_flag_leave_unterminated, &errors);
4962 do_test(errors.size() == 1 && errors.at(0).code == parse_error_tokenizer_unterminated_subshell);
4963
4964 errors.clear();
4965 ast = ast_t::parse(L"begin; echo '", parse_flag_leave_unterminated, &errors);
4966 do_test(errors.size() == 1 && errors.at(0).code == parse_error_tokenizer_unterminated_quote);
4967 }
4968
test_new_parser_errors()4969 static void test_new_parser_errors() {
4970 say(L"Testing new parser error reporting");
4971 const struct {
4972 const wchar_t *src;
4973 parse_error_code_t code;
4974 } tests[] = {
4975 {L"echo 'abc", parse_error_tokenizer_unterminated_quote},
4976 {L"'", parse_error_tokenizer_unterminated_quote},
4977 {L"echo (abc", parse_error_tokenizer_unterminated_subshell},
4978
4979 {L"end", parse_error_unbalancing_end},
4980 {L"echo hi ; end", parse_error_unbalancing_end},
4981
4982 {L"else", parse_error_unbalancing_else},
4983 {L"if true ; end ; else", parse_error_unbalancing_else},
4984
4985 {L"case", parse_error_unbalancing_case},
4986 {L"if true ; case ; end", parse_error_generic},
4987
4988 {L"true | and", parse_error_andor_in_pipeline},
4989
4990 {L"a=", parse_error_bare_variable_assignment},
4991 };
4992
4993 for (const auto &test : tests) {
4994 const wcstring src = test.src;
4995 parse_error_code_t expected_code = test.code;
4996
4997 parse_error_list_t errors;
4998 auto ast = ast::ast_t::parse(src, parse_flag_none, &errors);
4999 if (!ast.errored()) {
5000 err(L"Source '%ls' was expected to fail to parse, but succeeded", src.c_str());
5001 }
5002
5003 if (errors.size() != 1) {
5004 err(L"Source '%ls' was expected to produce 1 error, but instead produced %lu errors",
5005 src.c_str(), errors.size());
5006 for (const auto &err : errors) {
5007 fprintf(stderr, "%ls\n", err.describe(src, false).c_str());
5008 }
5009 } else if (errors.at(0).code != expected_code) {
5010 err(L"Source '%ls' was expected to produce error code %lu, but instead produced error "
5011 L"code %lu",
5012 src.c_str(), expected_code, (unsigned long)errors.at(0).code);
5013 for (const auto &error : errors) {
5014 err(L"\t\t%ls", error.describe(src, true).c_str());
5015 }
5016 }
5017 }
5018 }
5019
5020 // Given a format string, returns a list of non-empty strings separated by format specifiers. The
5021 // format specifiers themselves are omitted.
separate_by_format_specifiers(const wchar_t * format)5022 static wcstring_list_t separate_by_format_specifiers(const wchar_t *format) {
5023 wcstring_list_t result;
5024 const wchar_t *cursor = format;
5025 const wchar_t *end = format + std::wcslen(format);
5026 while (cursor < end) {
5027 const wchar_t *next_specifier = std::wcschr(cursor, '%');
5028 if (next_specifier == NULL) {
5029 next_specifier = end;
5030 }
5031 assert(next_specifier != NULL);
5032
5033 // Don't return empty strings.
5034 if (next_specifier > cursor) {
5035 result.push_back(wcstring(cursor, next_specifier - cursor));
5036 }
5037
5038 // Walk over the format specifier (if any).
5039 cursor = next_specifier;
5040 if (*cursor != '%') {
5041 continue;
5042 }
5043
5044 cursor++;
5045 // Flag
5046 if (std::wcschr(L"#0- +'", *cursor)) cursor++;
5047 // Minimum field width
5048 while (iswdigit(*cursor)) cursor++;
5049 // Precision
5050 if (*cursor == L'.') {
5051 cursor++;
5052 while (iswdigit(*cursor)) cursor++;
5053 }
5054 // Length modifier
5055 if (!std::wcsncmp(cursor, L"ll", 2) || !std::wcsncmp(cursor, L"hh", 2)) {
5056 cursor += 2;
5057 } else if (std::wcschr(L"hljtzqL", *cursor)) {
5058 cursor++;
5059 }
5060 // The format specifier itself. We allow any character except NUL.
5061 if (*cursor != L'\0') {
5062 cursor += 1;
5063 }
5064 assert(cursor <= end);
5065 }
5066 return result;
5067 }
5068
5069 // Given a format string 'format', return true if the string may have been produced by that format
5070 // string. We do this by splitting the format string around the format specifiers, and then ensuring
5071 // that each of the remaining chunks is found (in order) in the string.
string_matches_format(const wcstring & string,const wchar_t * format)5072 static bool string_matches_format(const wcstring &string, const wchar_t *format) {
5073 bool result = true;
5074 wcstring_list_t components = separate_by_format_specifiers(format);
5075 size_t idx = 0;
5076 for (const auto &component : components) {
5077 size_t where = string.find(component, idx);
5078 if (where == wcstring::npos) {
5079 result = false;
5080 break;
5081 }
5082 idx = where + component.size();
5083 assert(idx <= string.size());
5084 }
5085 return result;
5086 }
5087
test_error_messages()5088 static void test_error_messages() {
5089 say(L"Testing error messages");
5090 const struct error_test_t {
5091 const wchar_t *src;
5092 const wchar_t *error_text_format;
5093 } error_tests[] = {{L"echo $^", ERROR_BAD_VAR_CHAR1},
5094 {L"echo foo${a}bar", ERROR_BRACKETED_VARIABLE1},
5095 {L"echo foo\"${a}\"bar", ERROR_BRACKETED_VARIABLE_QUOTED1},
5096 {L"echo foo\"${\"bar", ERROR_BAD_VAR_CHAR1},
5097 {L"echo $?", ERROR_NOT_STATUS},
5098 {L"echo $$", ERROR_NOT_PID},
5099 {L"echo $#", ERROR_NOT_ARGV_COUNT},
5100 {L"echo $@", ERROR_NOT_ARGV_AT},
5101 {L"echo $*", ERROR_NOT_ARGV_STAR},
5102 {L"echo $", ERROR_NO_VAR_NAME},
5103 {L"echo foo\"$\"bar", ERROR_NO_VAR_NAME},
5104 {L"echo \"foo\"$\"bar\"", ERROR_NO_VAR_NAME},
5105 {L"echo foo $ bar", ERROR_NO_VAR_NAME},
5106 {L"echo foo$(foo)bar", ERROR_BAD_VAR_SUBCOMMAND1},
5107 {L"echo \"foo$(foo)bar\"", ERROR_BAD_VAR_SUBCOMMAND1}};
5108
5109 parse_error_list_t errors;
5110 for (const auto &test : error_tests) {
5111 errors.clear();
5112 parse_util_detect_errors(test.src, &errors);
5113 do_test(!errors.empty());
5114 if (!errors.empty()) {
5115 do_test1(string_matches_format(errors.at(0).text, test.error_text_format), test.src);
5116 }
5117 }
5118 }
5119
test_highlighting()5120 static void test_highlighting() {
5121 say(L"Testing syntax highlighting");
5122 if (!pushd("test/fish_highlight_test/")) return;
5123 cleanup_t pop{[] { popd(); }};
5124 if (system("mkdir -p dir")) err(L"mkdir failed");
5125 if (system("touch foo")) err(L"touch failed");
5126 if (system("touch bar")) err(L"touch failed");
5127
5128 // Here are the components of our source and the colors we expect those to be.
5129 struct highlight_component_t {
5130 const wchar_t *txt;
5131 highlight_spec_t color;
5132 bool nospace;
5133 highlight_component_t(const wchar_t *txt, highlight_spec_t color, bool nospace = false)
5134 : txt(txt), color(color), nospace(nospace) {}
5135 };
5136 const bool ns = true;
5137
5138 using highlight_component_list_t = std::vector<highlight_component_t>;
5139 std::vector<highlight_component_list_t> highlight_tests;
5140
5141 highlight_spec_t param_valid_path{highlight_role_t::param};
5142 param_valid_path.valid_path = true;
5143
5144 highlight_tests.push_back({{L"echo", highlight_role_t::command},
5145 {L"./foo", param_valid_path},
5146 {L"&", highlight_role_t::statement_terminator}});
5147
5148 highlight_tests.push_back({
5149 {L"command", highlight_role_t::keyword},
5150 {L"echo", highlight_role_t::command},
5151 {L"abc", highlight_role_t::param},
5152 {L"foo", param_valid_path},
5153 {L"&", highlight_role_t::statement_terminator},
5154 });
5155
5156 highlight_tests.push_back({
5157 {L"if command", highlight_role_t::keyword},
5158 {L"ls", highlight_role_t::command},
5159 {L"; ", highlight_role_t::statement_terminator},
5160 {L"echo", highlight_role_t::command},
5161 {L"abc", highlight_role_t::param},
5162 {L"; ", highlight_role_t::statement_terminator},
5163 {L"/bin/definitely_not_a_command", highlight_role_t::error},
5164 {L"; ", highlight_role_t::statement_terminator},
5165 {L"end", highlight_role_t::keyword},
5166 });
5167
5168 // Verify that cd shows errors for non-directories.
5169 highlight_tests.push_back({
5170 {L"cd", highlight_role_t::command},
5171 {L"dir", param_valid_path},
5172 });
5173
5174 highlight_tests.push_back({
5175 {L"cd", highlight_role_t::command},
5176 {L"foo", highlight_role_t::error},
5177 });
5178
5179 highlight_tests.push_back({
5180 {L"cd", highlight_role_t::command},
5181 {L"--help", highlight_role_t::param},
5182 {L"-h", highlight_role_t::param},
5183 {L"definitely_not_a_directory", highlight_role_t::error},
5184 });
5185
5186 // Command substitutions.
5187 highlight_tests.push_back({
5188 {L"echo", highlight_role_t::command},
5189 {L"param1", highlight_role_t::param},
5190 {L"(", highlight_role_t::operat},
5191 {L"ls", highlight_role_t::command},
5192 {L"param2", highlight_role_t::param},
5193 {L")", highlight_role_t::operat},
5194 {L"|", highlight_role_t::statement_terminator},
5195 {L"cat", highlight_role_t::command},
5196 });
5197
5198 // Redirections substitutions.
5199 highlight_tests.push_back({
5200 {L"echo", highlight_role_t::command},
5201 {L"param1", highlight_role_t::param},
5202
5203 // Input redirection.
5204 {L"<", highlight_role_t::redirection},
5205 {L"/bin/echo", highlight_role_t::redirection},
5206
5207 // Output redirection to a valid fd.
5208 {L"1>&2", highlight_role_t::redirection},
5209
5210 // Output redirection to an invalid fd.
5211 {L"2>&", highlight_role_t::redirection},
5212 {L"LOL", highlight_role_t::error},
5213
5214 // Just a param, not a redirection.
5215 {L"test/blah", highlight_role_t::param},
5216
5217 // Input redirection from directory.
5218 {L"<", highlight_role_t::redirection},
5219 {L"test/", highlight_role_t::error},
5220
5221 // Output redirection to an invalid path.
5222 {L"3>", highlight_role_t::redirection},
5223 {L"/not/a/valid/path/nope", highlight_role_t::error},
5224
5225 // Output redirection to directory.
5226 {L"3>", highlight_role_t::redirection},
5227 {L"test/nope/", highlight_role_t::error},
5228
5229 // Redirections to overflow fd.
5230 {L"99999999999999999999>&2", highlight_role_t::error},
5231 {L"2>&", highlight_role_t::redirection},
5232 {L"99999999999999999999", highlight_role_t::error},
5233
5234 // Output redirection containing a command substitution.
5235 {L"4>", highlight_role_t::redirection},
5236 {L"(", highlight_role_t::operat},
5237 {L"echo", highlight_role_t::command},
5238 {L"test/somewhere", highlight_role_t::param},
5239 {L")", highlight_role_t::operat},
5240
5241 // Just another param.
5242 {L"param2", highlight_role_t::param},
5243 });
5244
5245 highlight_tests.push_back({
5246 {L"for", highlight_role_t::keyword},
5247 {L"x", highlight_role_t::param},
5248 {L"in", highlight_role_t::keyword},
5249 {L"set-by-for-1", highlight_role_t::param},
5250 {L"set-by-for-2", highlight_role_t::param},
5251 {L";", highlight_role_t::statement_terminator},
5252 {L"echo", highlight_role_t::command},
5253 {L">", highlight_role_t::redirection},
5254 {L"$x", highlight_role_t::redirection},
5255 {L";", highlight_role_t::statement_terminator},
5256 {L"end", highlight_role_t::keyword},
5257 });
5258
5259 highlight_tests.push_back({
5260 {L"set", highlight_role_t::command},
5261 {L"x", highlight_role_t::param},
5262 {L"set-by-set", highlight_role_t::param},
5263 {L";", highlight_role_t::statement_terminator},
5264 {L"echo", highlight_role_t::command},
5265 {L">", highlight_role_t::redirection},
5266 {L"$x", highlight_role_t::redirection},
5267 {L"2>", highlight_role_t::redirection},
5268 {L"$totally_not_x", highlight_role_t::error},
5269 {L"<", highlight_role_t::redirection},
5270 {L"$x_but_its_an_impostor", highlight_role_t::error},
5271 });
5272
5273 highlight_tests.push_back({
5274 {L"x", highlight_role_t::param, ns},
5275 {L"=", highlight_role_t::operat, ns},
5276 {L"set-by-variable-override", highlight_role_t::param, ns},
5277 {L"echo", highlight_role_t::command},
5278 {L">", highlight_role_t::redirection},
5279 {L"$x", highlight_role_t::redirection},
5280 });
5281
5282 highlight_tests.push_back({
5283 {L"end", highlight_role_t::error},
5284 {L";", highlight_role_t::statement_terminator},
5285 {L"if", highlight_role_t::keyword},
5286 {L"end", highlight_role_t::error},
5287 });
5288
5289 highlight_tests.push_back({
5290 {L"echo", highlight_role_t::command},
5291 {L"'", highlight_role_t::error},
5292 {L"single_quote", highlight_role_t::quote},
5293 {L"$stuff", highlight_role_t::quote},
5294 });
5295
5296 highlight_tests.push_back({
5297 {L"echo", highlight_role_t::command},
5298 {L"\"", highlight_role_t::error},
5299 {L"double_quote", highlight_role_t::quote},
5300 {L"$stuff", highlight_role_t::operat},
5301 });
5302
5303 highlight_tests.push_back({
5304 {L"echo", highlight_role_t::command},
5305 {L"$foo", highlight_role_t::operat},
5306 {L"\"", highlight_role_t::quote},
5307 {L"$bar", highlight_role_t::operat},
5308 {L"\"", highlight_role_t::quote},
5309 {L"$baz[", highlight_role_t::operat},
5310 {L"1 2..3", highlight_role_t::param},
5311 {L"]", highlight_role_t::operat},
5312 });
5313
5314 highlight_tests.push_back({
5315 {L"for", highlight_role_t::keyword},
5316 {L"i", highlight_role_t::param},
5317 {L"in", highlight_role_t::keyword},
5318 {L"1 2 3", highlight_role_t::param},
5319 {L";", highlight_role_t::statement_terminator},
5320 {L"end", highlight_role_t::keyword},
5321 });
5322
5323 highlight_tests.push_back({
5324 {L"echo", highlight_role_t::command},
5325 {L"$$foo[", highlight_role_t::operat},
5326 {L"1", highlight_role_t::param},
5327 {L"][", highlight_role_t::operat},
5328 {L"2", highlight_role_t::param},
5329 {L"]", highlight_role_t::operat},
5330 {L"[3]", highlight_role_t::param}, // two dollar signs, so last one is not an expansion
5331 });
5332
5333 highlight_tests.push_back({
5334 {L"cat", highlight_role_t::command},
5335 {L"/dev/null", param_valid_path},
5336 {L"|", highlight_role_t::statement_terminator},
5337 // This is bogus, but we used to use "less" here and that doesn't have to be installed.
5338 {L"cat", highlight_role_t::command},
5339 {L"2>", highlight_role_t::redirection},
5340 });
5341
5342 highlight_tests.push_back({
5343 {L"if", highlight_role_t::keyword},
5344 {L"true", highlight_role_t::command},
5345 {L"&&", highlight_role_t::operat},
5346 {L"false", highlight_role_t::command},
5347 {L";", highlight_role_t::statement_terminator},
5348 {L"or", highlight_role_t::operat},
5349 {L"false", highlight_role_t::command},
5350 {L"||", highlight_role_t::operat},
5351 {L"true", highlight_role_t::command},
5352 {L";", highlight_role_t::statement_terminator},
5353 {L"and", highlight_role_t::operat},
5354 {L"not", highlight_role_t::operat},
5355 {L"!", highlight_role_t::operat},
5356 {L"true", highlight_role_t::command},
5357 {L";", highlight_role_t::statement_terminator},
5358 {L"end", highlight_role_t::keyword},
5359 });
5360
5361 highlight_tests.push_back({
5362 {L"echo", highlight_role_t::command},
5363 {L"%self", highlight_role_t::operat},
5364 {L"not%self", highlight_role_t::param},
5365 {L"self%not", highlight_role_t::param},
5366 });
5367
5368 highlight_tests.push_back({
5369 {L"false", highlight_role_t::command},
5370 {L"&|", highlight_role_t::statement_terminator},
5371 {L"true", highlight_role_t::command},
5372 });
5373
5374 highlight_tests.push_back({
5375 {L"HOME", highlight_role_t::param},
5376 {L"=", highlight_role_t::operat, ns},
5377 {L".", highlight_role_t::param, ns},
5378 {L"VAR1", highlight_role_t::param},
5379 {L"=", highlight_role_t::operat, ns},
5380 {L"VAL1", highlight_role_t::param, ns},
5381 {L"VAR", highlight_role_t::param},
5382 {L"=", highlight_role_t::operat, ns},
5383 {L"false", highlight_role_t::command},
5384 {L"|&", highlight_role_t::error},
5385 {L"true", highlight_role_t::command},
5386 {L"stuff", highlight_role_t::param},
5387 });
5388
5389 highlight_tests.push_back({
5390 {L"echo", highlight_role_t::command},
5391 {L")", highlight_role_t::error},
5392 });
5393
5394 highlight_tests.push_back({
5395 {L"echo", highlight_role_t::command},
5396 {L"stuff", highlight_role_t::param},
5397 {L"# comment", highlight_role_t::comment},
5398 });
5399
5400 // Overlong paths don't crash (#7837).
5401 const wcstring overlong = get_overlong_path();
5402 highlight_tests.push_back({
5403 {L"touch", highlight_role_t::command},
5404 {overlong.c_str(), highlight_role_t::param},
5405 });
5406
5407 highlight_tests.push_back({
5408 {L"a", highlight_role_t::param},
5409 {L"=", highlight_role_t::operat, ns},
5410 });
5411
5412 auto &vars = parser_t::principal_parser().vars();
5413 // Verify variables and wildcards in commands using /bin/cat.
5414 vars.set(L"VARIABLE_IN_COMMAND", ENV_LOCAL, {L"a"});
5415 vars.set(L"VARIABLE_IN_COMMAND2", ENV_LOCAL, {L"at"});
5416 highlight_tests.push_back(
5417 {{L"/bin/ca", highlight_role_t::command, ns}, {L"*", highlight_role_t::operat, ns}});
5418
5419 highlight_tests.push_back({{L"/bin/c", highlight_role_t::command, ns},
5420 {L"{$VARIABLE_IN_COMMAND}", highlight_role_t::operat, ns},
5421 {L"*", highlight_role_t::operat, ns}});
5422
5423 highlight_tests.push_back({{L"/bin/c", highlight_role_t::command, ns},
5424 {L"{$VARIABLE_IN_COMMAND}", highlight_role_t::operat, ns},
5425 {L"*", highlight_role_t::operat, ns}});
5426
5427 highlight_tests.push_back({{L"/bin/c", highlight_role_t::command, ns},
5428 {L"$VARIABLE_IN_COMMAND2", highlight_role_t::operat, ns}});
5429
5430 highlight_tests.push_back({{L"$EMPTY_VARIABLE", highlight_role_t::error}});
5431 highlight_tests.push_back({{L"\"$EMPTY_VARIABLE\"", highlight_role_t::error}});
5432
5433 for (const highlight_component_list_t &components : highlight_tests) {
5434 // Generate the text.
5435 wcstring text;
5436 std::vector<highlight_spec_t> expected_colors;
5437 for (const highlight_component_t &comp : components) {
5438 if (!text.empty() && !comp.nospace) {
5439 text.push_back(L' ');
5440 expected_colors.push_back(highlight_spec_t{});
5441 }
5442 text.append(comp.txt);
5443 expected_colors.resize(text.size(), comp.color);
5444 }
5445 do_test(expected_colors.size() == text.size());
5446
5447 std::vector<highlight_spec_t> colors(text.size());
5448 highlight_shell(text, colors, operation_context_t{vars}, true /* io_ok */);
5449
5450 if (expected_colors.size() != colors.size()) {
5451 err(L"Color vector has wrong size! Expected %lu, actual %lu", expected_colors.size(),
5452 colors.size());
5453 }
5454 do_test(expected_colors.size() == colors.size());
5455 for (size_t i = 0; i < text.size(); i++) {
5456 // Hackish space handling. We don't care about the colors in spaces.
5457 if (text.at(i) == L' ') continue;
5458
5459 if (expected_colors.at(i) != colors.at(i)) {
5460 const wcstring spaces(i, L' ');
5461 err(L"Wrong color in test at index %lu in text (expected %#x, actual "
5462 L"%#x):\n%ls\n%ls^",
5463 i, expected_colors.at(i), colors.at(i), text.c_str(), spaces.c_str());
5464 }
5465 }
5466 }
5467 vars.remove(L"VARIABLE_IN_COMMAND", ENV_DEFAULT);
5468 vars.remove(L"VARIABLE_IN_COMMAND2", ENV_DEFAULT);
5469 }
5470
test_split_string_tok()5471 static void test_split_string_tok() {
5472 say(L"Testing split_string_tok");
5473 wcstring_list_t splits;
5474 splits = split_string_tok(L" hello \t world", L" \t\n");
5475 do_test((splits == wcstring_list_t{L"hello", L"world"}));
5476
5477 splits = split_string_tok(L" stuff ", wcstring(L" "), 0);
5478 do_test((splits == wcstring_list_t{}));
5479
5480 splits = split_string_tok(L" stuff ", wcstring(L" "), 1);
5481 do_test((splits == wcstring_list_t{L" stuff "}));
5482
5483 splits = split_string_tok(L" hello \t world andstuff ", L" \t\n", 3);
5484 do_test((splits == wcstring_list_t{L"hello", L"world", L" andstuff "}));
5485
5486 // NUL chars are OK.
5487 wcstring nullstr = L" hello X world";
5488 nullstr.at(nullstr.find(L'X')) = L'\0';
5489 splits = split_string_tok(nullstr, wcstring(L" \0", 2));
5490 do_test((splits == wcstring_list_t{L"hello", L"world"}));
5491 }
5492
test_wwrite_to_fd()5493 static void test_wwrite_to_fd() {
5494 say(L"Testing wwrite_to_fd");
5495 char t[] = "/tmp/fish_test_wwrite.XXXXXX";
5496 autoclose_fd_t tmpfd{mkstemp(t)};
5497 if (!tmpfd.valid()) {
5498 err(L"Unable to create temporary file");
5499 return;
5500 }
5501 tmpfd.close();
5502
5503 size_t sizes[] = {0, 1, 2, 3, 5, 13, 23, 64, 128, 255, 4096, 4096 * 2};
5504 for (size_t size : sizes) {
5505 autoclose_fd_t fd{open(t, O_RDWR | O_TRUNC | O_CREAT, 0666)};
5506 if (!fd.valid()) {
5507 wperror(L"open");
5508 err(L"Unable to open temporary file");
5509 return;
5510 }
5511 wcstring input{};
5512 for (size_t i = 0; i < size; i++) {
5513 input.push_back(wchar_t(random()));
5514 }
5515
5516 ssize_t amt = wwrite_to_fd(input, fd.fd());
5517 if (amt < 0) {
5518 wperror(L"write");
5519 err(L"Unable to write to temporary file");
5520 return;
5521 }
5522 std::string narrow = wcs2string(input);
5523 size_t expected_size = narrow.size();
5524 do_test(static_cast<size_t>(amt) == expected_size);
5525
5526 if (lseek(fd.fd(), 0, SEEK_SET) < 0) {
5527 wperror(L"seek");
5528 err(L"Unable to seek temporary file");
5529 return;
5530 }
5531
5532 std::string contents(expected_size, '\0');
5533 ssize_t read_amt = read(fd.fd(), &contents[0], expected_size);
5534 do_test(read_amt >= 0 && static_cast<size_t>(read_amt) == expected_size);
5535 }
5536 (void)remove(t);
5537 }
5538
test_pcre2_escape()5539 static void test_pcre2_escape() {
5540 say(L"Testing escaping strings as pcre2 literals");
5541 // plain text should not be needlessly escaped
5542 auto input = L"hello world!";
5543 auto escaped = escape_string(input, 0, STRING_STYLE_REGEX);
5544 if (escaped != input) {
5545 err(L"Input string %ls unnecessarily PCRE2 escaped as %ls", input, escaped.c_str());
5546 }
5547
5548 // all the following are intended to be ultimately matched literally - even if they don't look
5549 // like that's the intent - so we escape them.
5550 const wchar_t *const tests[][2] = {
5551 {L".ext", L"\\.ext"},
5552 {L"{word}", L"\\{word\\}"},
5553 {L"hola-mundo", L"hola\\-mundo"},
5554 {L"$17.42 is your total?", L"\\$17\\.42 is your total\\?"},
5555 {L"not really escaped\\?", L"not really escaped\\\\\\?"},
5556 };
5557
5558 for (const auto &test : tests) {
5559 auto escaped = escape_string(test[0], 0, STRING_STYLE_REGEX);
5560 if (escaped != test[1]) {
5561 err(L"pcre2_escape error: pcre2_escape(%ls) -> %ls, expected %ls", test[0],
5562 escaped.c_str(), test[1]);
5563 }
5564 }
5565 }
5566
5567 maybe_t<int> builtin_string(parser_t &parser, io_streams_t &streams, const wchar_t **argv);
run_one_string_test(const wchar_t * const * argv_raw,int expected_rc,const wchar_t * expected_out)5568 static void run_one_string_test(const wchar_t *const *argv_raw, int expected_rc,
5569 const wchar_t *expected_out) {
5570 // Copy to a null terminated array, as builtin_string may wish to rearrange our pointers.
5571 wcstring_list_t argv_list(argv_raw, argv_raw + null_terminated_array_length(argv_raw));
5572 null_terminated_array_t<wchar_t> argv(argv_list);
5573
5574 parser_t &parser = parser_t::principal_parser();
5575 string_output_stream_t outs{};
5576 null_output_stream_t errs{};
5577 io_streams_t streams(outs, errs);
5578 streams.stdin_is_directly_redirected = false; // read from argv instead of stdin
5579 maybe_t<int> rc = builtin_string(parser, streams, argv.get());
5580
5581 wcstring args;
5582 for (const wcstring &arg : argv_list) {
5583 args += escape_string(arg, ESCAPE_ALL) + L' ';
5584 }
5585 args.resize(args.size() - 1);
5586
5587 if (rc != expected_rc) {
5588 std::wstring got = rc ? std::to_wstring(rc.value()) : L"nothing";
5589 err(L"Test failed on line %lu: [%ls]: expected return code %d but got %s", __LINE__,
5590 args.c_str(), expected_rc, got.c_str());
5591 } else if (outs.contents() != expected_out) {
5592 err(L"Test failed on line %lu: [%ls]: expected [%ls] but got [%ls]", __LINE__, args.c_str(),
5593 escape_string(expected_out, ESCAPE_ALL).c_str(),
5594 escape_string(outs.contents(), ESCAPE_ALL).c_str());
5595 }
5596 }
5597
test_string()5598 static void test_string() {
5599 say(L"Testing builtin_string");
5600 const struct string_test {
5601 const wchar_t *argv[15];
5602 int expected_rc;
5603 const wchar_t *expected_out;
5604 } string_tests[] = {
5605 {{L"string", L"escape", 0}, STATUS_CMD_ERROR, L""},
5606 {{L"string", L"escape", L"", 0}, STATUS_CMD_OK, L"''\n"},
5607 {{L"string", L"escape", L"-n", L"", 0}, STATUS_CMD_OK, L"\n"},
5608 {{L"string", L"escape", L"a", 0}, STATUS_CMD_OK, L"a\n"},
5609 {{L"string", L"escape", L"\x07", 0}, STATUS_CMD_OK, L"\\cg\n"},
5610 {{L"string", L"escape", L"\"x\"", 0}, STATUS_CMD_OK, L"'\"x\"'\n"},
5611 {{L"string", L"escape", L"hello world", 0}, STATUS_CMD_OK, L"'hello world'\n"},
5612 {{L"string", L"escape", L"-n", L"hello world", 0}, STATUS_CMD_OK, L"hello\\ world\n"},
5613 {{L"string", L"escape", L"hello", L"world", 0}, STATUS_CMD_OK, L"hello\nworld\n"},
5614 {{L"string", L"escape", L"-n", L"~", 0}, STATUS_CMD_OK, L"\\~\n"},
5615
5616 {{L"string", L"join", 0}, STATUS_INVALID_ARGS, L""},
5617 {{L"string", L"join", L"", 0}, STATUS_CMD_ERROR, L""},
5618 {{L"string", L"join", L"", L"", L"", L"", 0}, STATUS_CMD_OK, L"\n"},
5619 {{L"string", L"join", L"", L"a", L"b", L"c", 0}, STATUS_CMD_OK, L"abc\n"},
5620 {{L"string", L"join", L".", L"fishshell", L"com", 0}, STATUS_CMD_OK, L"fishshell.com\n"},
5621 {{L"string", L"join", L"/", L"usr", 0}, STATUS_CMD_ERROR, L"usr\n"},
5622 {{L"string", L"join", L"/", L"usr", L"local", L"bin", 0},
5623 STATUS_CMD_OK,
5624 L"usr/local/bin\n"},
5625 {{L"string", L"join", L"...", L"3", L"2", L"1", 0}, STATUS_CMD_OK, L"3...2...1\n"},
5626 {{L"string", L"join", L"-q", 0}, STATUS_INVALID_ARGS, L""},
5627 {{L"string", L"join", L"-q", L".", 0}, STATUS_CMD_ERROR, L""},
5628 {{L"string", L"join", L"-q", L".", L".", 0}, STATUS_CMD_ERROR, L""},
5629
5630 {{L"string", L"length", 0}, STATUS_CMD_ERROR, L""},
5631 {{L"string", L"length", L"", 0}, STATUS_CMD_ERROR, L"0\n"},
5632 {{L"string", L"length", L"", L"", L"", 0}, STATUS_CMD_ERROR, L"0\n0\n0\n"},
5633 {{L"string", L"length", L"a", 0}, STATUS_CMD_OK, L"1\n"},
5634 {{L"string", L"length", L"\U0002008A", 0}, STATUS_CMD_OK, L"1\n"},
5635 {{L"string", L"length", L"um", L"dois", L"três", 0}, STATUS_CMD_OK, L"2\n4\n4\n"},
5636 {{L"string", L"length", L"um", L"dois", L"três", 0}, STATUS_CMD_OK, L"2\n4\n4\n"},
5637 {{L"string", L"length", L"-q", 0}, STATUS_CMD_ERROR, L""},
5638 {{L"string", L"length", L"-q", L"", 0}, STATUS_CMD_ERROR, L""},
5639 {{L"string", L"length", L"-q", L"a", 0}, STATUS_CMD_OK, L""},
5640
5641 {{L"string", L"match", 0}, STATUS_INVALID_ARGS, L""},
5642 {{L"string", L"match", L"", 0}, STATUS_CMD_ERROR, L""},
5643 {{L"string", L"match", L"", L"", 0}, STATUS_CMD_OK, L"\n"},
5644 {{L"string", L"match", L"?", L"a", 0}, STATUS_CMD_OK, L"a\n"},
5645 {{L"string", L"match", L"*", L"", 0}, STATUS_CMD_OK, L"\n"},
5646 {{L"string", L"match", L"**", L"", 0}, STATUS_CMD_OK, L"\n"},
5647 {{L"string", L"match", L"*", L"xyzzy", 0}, STATUS_CMD_OK, L"xyzzy\n"},
5648 {{L"string", L"match", L"**", L"plugh", 0}, STATUS_CMD_OK, L"plugh\n"},
5649 {{L"string", L"match", L"a*b", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
5650 {{L"string", L"match", L"a??b", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
5651 {{L"string", L"match", L"-i", L"a??B", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
5652 {{L"string", L"match", L"-i", L"a??b", L"Axxb", 0}, STATUS_CMD_OK, L"Axxb\n"},
5653 {{L"string", L"match", L"a*", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
5654 {{L"string", L"match", L"*a", L"xxa", 0}, STATUS_CMD_OK, L"xxa\n"},
5655 {{L"string", L"match", L"*a*", L"axa", 0}, STATUS_CMD_OK, L"axa\n"},
5656 {{L"string", L"match", L"*a*", L"xax", 0}, STATUS_CMD_OK, L"xax\n"},
5657 {{L"string", L"match", L"*a*", L"bxa", 0}, STATUS_CMD_OK, L"bxa\n"},
5658 {{L"string", L"match", L"*a", L"a", 0}, STATUS_CMD_OK, L"a\n"},
5659 {{L"string", L"match", L"a*", L"a", 0}, STATUS_CMD_OK, L"a\n"},
5660 {{L"string", L"match", L"a*b*c", L"axxbyyc", 0}, STATUS_CMD_OK, L"axxbyyc\n"},
5661 {{L"string", L"match", L"\\*", L"*", 0}, STATUS_CMD_OK, L"*\n"},
5662 {{L"string", L"match", L"a*\\", L"abc\\", 0}, STATUS_CMD_OK, L"abc\\\n"},
5663 {{L"string", L"match", L"a*\\?", L"abc?", 0}, STATUS_CMD_OK, L"abc?\n"},
5664
5665 {{L"string", L"match", L"?", L"", 0}, STATUS_CMD_ERROR, L""},
5666 {{L"string", L"match", L"?", L"ab", 0}, STATUS_CMD_ERROR, L""},
5667 {{L"string", L"match", L"??", L"a", 0}, STATUS_CMD_ERROR, L""},
5668 {{L"string", L"match", L"?a", L"a", 0}, STATUS_CMD_ERROR, L""},
5669 {{L"string", L"match", L"a?", L"a", 0}, STATUS_CMD_ERROR, L""},
5670 {{L"string", L"match", L"a??B", L"axxb", 0}, STATUS_CMD_ERROR, L""},
5671 {{L"string", L"match", L"a*b", L"axxbc", 0}, STATUS_CMD_ERROR, L""},
5672 {{L"string", L"match", L"*b", L"bbba", 0}, STATUS_CMD_ERROR, L""},
5673 {{L"string", L"match", L"0x[0-9a-fA-F][0-9a-fA-F]", L"0xbad", 0}, STATUS_CMD_ERROR, L""},
5674
5675 {{L"string", L"match", L"-a", L"*", L"ab", L"cde", 0}, STATUS_CMD_OK, L"ab\ncde\n"},
5676 {{L"string", L"match", L"*", L"ab", L"cde", 0}, STATUS_CMD_OK, L"ab\ncde\n"},
5677 {{L"string", L"match", L"-n", L"*d*", L"cde", 0}, STATUS_CMD_OK, L"1 3\n"},
5678 {{L"string", L"match", L"-n", L"*x*", L"cde", 0}, STATUS_CMD_ERROR, L""},
5679 {{L"string", L"match", L"-q", L"a*", L"b", L"c", 0}, STATUS_CMD_ERROR, L""},
5680 {{L"string", L"match", L"-q", L"a*", L"b", L"a", 0}, STATUS_CMD_OK, L""},
5681
5682 {{L"string", L"match", L"-r", 0}, STATUS_INVALID_ARGS, L""},
5683 {{L"string", L"match", L"-r", L"", 0}, STATUS_CMD_ERROR, L""},
5684 {{L"string", L"match", L"-r", L"", L"", 0}, STATUS_CMD_OK, L"\n"},
5685 {{L"string", L"match", L"-r", L".", L"a", 0}, STATUS_CMD_OK, L"a\n"},
5686 {{L"string", L"match", L"-r", L".*", L"", 0}, STATUS_CMD_OK, L"\n"},
5687 {{L"string", L"match", L"-r", L"a*b", L"b", 0}, STATUS_CMD_OK, L"b\n"},
5688 {{L"string", L"match", L"-r", L"a*b", L"aab", 0}, STATUS_CMD_OK, L"aab\n"},
5689 {{L"string", L"match", L"-r", L"-i", L"a*b", L"Aab", 0}, STATUS_CMD_OK, L"Aab\n"},
5690 {{L"string", L"match", L"-r", L"-a", L"a[bc]", L"abadac", 0}, STATUS_CMD_OK, L"ab\nac\n"},
5691 {{L"string", L"match", L"-r", L"a", L"xaxa", L"axax", 0}, STATUS_CMD_OK, L"a\na\n"},
5692 {{L"string", L"match", L"-r", L"-a", L"a", L"xaxa", L"axax", 0},
5693 STATUS_CMD_OK,
5694 L"a\na\na\na\n"},
5695 {{L"string", L"match", L"-r", L"a[bc]", L"abadac", 0}, STATUS_CMD_OK, L"ab\n"},
5696 {{L"string", L"match", L"-r", L"-q", L"a[bc]", L"abadac", 0}, STATUS_CMD_OK, L""},
5697 {{L"string", L"match", L"-r", L"-q", L"a[bc]", L"ad", 0}, STATUS_CMD_ERROR, L""},
5698 {{L"string", L"match", L"-r", L"(a+)b(c)", L"aabc", 0}, STATUS_CMD_OK, L"aabc\naa\nc\n"},
5699 {{L"string", L"match", L"-r", L"-a", L"(a)b(c)", L"abcabc", 0},
5700 STATUS_CMD_OK,
5701 L"abc\na\nc\nabc\na\nc\n"},
5702 {{L"string", L"match", L"-r", L"(a)b(c)", L"abcabc", 0}, STATUS_CMD_OK, L"abc\na\nc\n"},
5703 {{L"string", L"match", L"-r", L"(a|(z))(bc)", L"abc", 0}, STATUS_CMD_OK, L"abc\na\nbc\n"},
5704 {{L"string", L"match", L"-r", L"-n", L"a", L"ada", L"dad", 0},
5705 STATUS_CMD_OK,
5706 L"1 1\n2 1\n"},
5707 {{L"string", L"match", L"-r", L"-n", L"-a", L"a", L"bacadae", 0},
5708 STATUS_CMD_OK,
5709 L"2 1\n4 1\n6 1\n"},
5710 {{L"string", L"match", L"-r", L"-n", L"(a).*(b)", L"a---b", 0},
5711 STATUS_CMD_OK,
5712 L"1 5\n1 1\n5 1\n"},
5713 {{L"string", L"match", L"-r", L"-n", L"(a)(b)", L"ab", 0},
5714 STATUS_CMD_OK,
5715 L"1 2\n1 1\n2 1\n"},
5716 {{L"string", L"match", L"-r", L"-n", L"(a)(b)", L"abab", 0},
5717 STATUS_CMD_OK,
5718 L"1 2\n1 1\n2 1\n"},
5719 {{L"string", L"match", L"-r", L"-n", L"-a", L"(a)(b)", L"abab", 0},
5720 STATUS_CMD_OK,
5721 L"1 2\n1 1\n2 1\n3 2\n3 1\n4 1\n"},
5722 {{L"string", L"match", L"-r", L"*", L"", 0}, STATUS_INVALID_ARGS, L""},
5723 {{L"string", L"match", L"-r", L"-a", L"a*", L"b", 0}, STATUS_CMD_OK, L"\n\n"},
5724 {{L"string", L"match", L"-r", L"foo\\Kbar", L"foobar", 0}, STATUS_CMD_OK, L"bar\n"},
5725 {{L"string", L"match", L"-r", L"(foo)\\Kbar", L"foobar", 0}, STATUS_CMD_OK, L"bar\nfoo\n"},
5726 {{L"string", L"match", L"-r", L"(?=ab\\K)", L"ab", 0}, STATUS_CMD_OK, L"\n"},
5727 {{L"string", L"match", L"-r", L"(?=ab\\K)..(?=cd\\K)", L"abcd", 0}, STATUS_CMD_OK, L"\n"},
5728
5729 {{L"string", L"replace", 0}, STATUS_INVALID_ARGS, L""},
5730 {{L"string", L"replace", L"", 0}, STATUS_INVALID_ARGS, L""},
5731 {{L"string", L"replace", L"", L"", 0}, STATUS_CMD_ERROR, L""},
5732 {{L"string", L"replace", L"", L"", L"", 0}, STATUS_CMD_ERROR, L"\n"},
5733 {{L"string", L"replace", L"", L"", L" ", 0}, STATUS_CMD_ERROR, L" \n"},
5734 {{L"string", L"replace", L"a", L"b", L"", 0}, STATUS_CMD_ERROR, L"\n"},
5735 {{L"string", L"replace", L"a", L"b", L"a", 0}, STATUS_CMD_OK, L"b\n"},
5736 {{L"string", L"replace", L"a", L"b", L"xax", 0}, STATUS_CMD_OK, L"xbx\n"},
5737 {{L"string", L"replace", L"a", L"b", L"xax", L"axa", 0}, STATUS_CMD_OK, L"xbx\nbxa\n"},
5738 {{L"string", L"replace", L"bar", L"x", L"red barn", 0}, STATUS_CMD_OK, L"red xn\n"},
5739 {{L"string", L"replace", L"x", L"bar", L"red xn", 0}, STATUS_CMD_OK, L"red barn\n"},
5740 {{L"string", L"replace", L"--", L"x", L"-", L"xyz", 0}, STATUS_CMD_OK, L"-yz\n"},
5741 {{L"string", L"replace", L"--", L"y", L"-", L"xyz", 0}, STATUS_CMD_OK, L"x-z\n"},
5742 {{L"string", L"replace", L"--", L"z", L"-", L"xyz", 0}, STATUS_CMD_OK, L"xy-\n"},
5743 {{L"string", L"replace", L"-i", L"z", L"X", L"_Z_", 0}, STATUS_CMD_OK, L"_X_\n"},
5744 {{L"string", L"replace", L"-a", L"a", L"A", L"aaa", 0}, STATUS_CMD_OK, L"AAA\n"},
5745 {{L"string", L"replace", L"-i", L"a", L"z", L"AAA", 0}, STATUS_CMD_OK, L"zAA\n"},
5746 {{L"string", L"replace", L"-q", L"x", L">x<", L"x", 0}, STATUS_CMD_OK, L""},
5747 {{L"string", L"replace", L"-a", L"x", L"", L"xxx", 0}, STATUS_CMD_OK, L"\n"},
5748 {{L"string", L"replace", L"-a", L"***", L"_", L"*****", 0}, STATUS_CMD_OK, L"_**\n"},
5749 {{L"string", L"replace", L"-a", L"***", L"***", L"******", 0}, STATUS_CMD_OK, L"******\n"},
5750 {{L"string", L"replace", L"-a", L"a", L"b", L"xax", L"axa", 0},
5751 STATUS_CMD_OK,
5752 L"xbx\nbxb\n"},
5753
5754 {{L"string", L"replace", L"-r", 0}, STATUS_INVALID_ARGS, L""},
5755 {{L"string", L"replace", L"-r", L"", 0}, STATUS_INVALID_ARGS, L""},
5756 {{L"string", L"replace", L"-r", L"", L"", 0}, STATUS_CMD_ERROR, L""},
5757 {{L"string", L"replace", L"-r", L"", L"", L"", 0}, STATUS_CMD_OK, L"\n"}, // pcre2 behavior
5758 {{L"string", L"replace", L"-r", L"", L"", L" ", 0},
5759 STATUS_CMD_OK,
5760 L" \n"}, // pcre2 behavior
5761 {{L"string", L"replace", L"-r", L"a", L"b", L"", 0}, STATUS_CMD_ERROR, L"\n"},
5762 {{L"string", L"replace", L"-r", L"a", L"b", L"a", 0}, STATUS_CMD_OK, L"b\n"},
5763 {{L"string", L"replace", L"-r", L".", L"x", L"abc", 0}, STATUS_CMD_OK, L"xbc\n"},
5764 {{L"string", L"replace", L"-r", L".", L"", L"abc", 0}, STATUS_CMD_OK, L"bc\n"},
5765 {{L"string", L"replace", L"-r", L"(\\w)(\\w)", L"$2$1", L"ab", 0}, STATUS_CMD_OK, L"ba\n"},
5766 {{L"string", L"replace", L"-r", L"(\\w)", L"$1$1", L"ab", 0}, STATUS_CMD_OK, L"aab\n"},
5767 {{L"string", L"replace", L"-r", L"-a", L".", L"x", L"abc", 0}, STATUS_CMD_OK, L"xxx\n"},
5768 {{L"string", L"replace", L"-r", L"-a", L"(\\w)", L"$1$1", L"ab", 0},
5769 STATUS_CMD_OK,
5770 L"aabb\n"},
5771 {{L"string", L"replace", L"-r", L"-a", L".", L"", L"abc", 0}, STATUS_CMD_OK, L"\n"},
5772 {{L"string", L"replace", L"-r", L"a", L"x", L"bc", L"cd", L"de", 0},
5773 STATUS_CMD_ERROR,
5774 L"bc\ncd\nde\n"},
5775 {{L"string", L"replace", L"-r", L"a", L"x", L"aba", L"caa", 0},
5776 STATUS_CMD_OK,
5777 L"xba\ncxa\n"},
5778 {{L"string", L"replace", L"-r", L"-a", L"a", L"x", L"aba", L"caa", 0},
5779 STATUS_CMD_OK,
5780 L"xbx\ncxx\n"},
5781 {{L"string", L"replace", L"-r", L"-i", L"A", L"b", L"xax", 0}, STATUS_CMD_OK, L"xbx\n"},
5782 {{L"string", L"replace", L"-r", L"-i", L"[a-z]", L".", L"1A2B", 0},
5783 STATUS_CMD_OK,
5784 L"1.2B\n"},
5785 {{L"string", L"replace", L"-r", L"A", L"b", L"xax", 0}, STATUS_CMD_ERROR, L"xax\n"},
5786 {{L"string", L"replace", L"-r", L"a", L"$1", L"a", 0}, STATUS_INVALID_ARGS, L""},
5787 {{L"string", L"replace", L"-r", L"(a)", L"$2", L"a", 0}, STATUS_INVALID_ARGS, L""},
5788 {{L"string", L"replace", L"-r", L"*", L".", L"a", 0}, STATUS_INVALID_ARGS, L""},
5789 {{L"string", L"replace", L"-ra", L"x", L"\\c", 0}, STATUS_CMD_ERROR, L""},
5790 {{L"string", L"replace", L"-r", L"^(.)", L"\t$1", L"abc", L"x", 0},
5791 STATUS_CMD_OK,
5792 L"\tabc\n\tx\n"},
5793
5794 {{L"string", L"split", 0}, STATUS_INVALID_ARGS, L""},
5795 {{L"string", L"split", L":", 0}, STATUS_CMD_ERROR, L""},
5796 {{L"string", L"split", L".", L"www.ch.ic.ac.uk", 0},
5797 STATUS_CMD_OK,
5798 L"www\nch\nic\nac\nuk\n"},
5799 {{L"string", L"split", L"..", L"....", 0}, STATUS_CMD_OK, L"\n\n\n"},
5800 {{L"string", L"split", L"-m", L"x", L"..", L"....", 0}, STATUS_INVALID_ARGS, L""},
5801 {{L"string", L"split", L"-m1", L"..", L"....", 0}, STATUS_CMD_OK, L"\n..\n"},
5802 {{L"string", L"split", L"-m0", L"/", L"/usr/local/bin/fish", 0},
5803 STATUS_CMD_ERROR,
5804 L"/usr/local/bin/fish\n"},
5805 {{L"string", L"split", L"-m2", L":", L"a:b:c:d", L"e:f:g:h", 0},
5806 STATUS_CMD_OK,
5807 L"a\nb\nc:d\ne\nf\ng:h\n"},
5808 {{L"string", L"split", L"-m1", L"-r", L"/", L"/usr/local/bin/fish", 0},
5809 STATUS_CMD_OK,
5810 L"/usr/local/bin\nfish\n"},
5811 {{L"string", L"split", L"-r", L".", L"www.ch.ic.ac.uk", 0},
5812 STATUS_CMD_OK,
5813 L"www\nch\nic\nac\nuk\n"},
5814 {{L"string", L"split", L"--", L"--", L"a--b---c----d", 0},
5815 STATUS_CMD_OK,
5816 L"a\nb\n-c\n\nd\n"},
5817 {{L"string", L"split", L"-r", L"..", L"....", 0}, STATUS_CMD_OK, L"\n\n\n"},
5818 {{L"string", L"split", L"-r", L"--", L"--", L"a--b---c----d", 0},
5819 STATUS_CMD_OK,
5820 L"a\nb-\nc\n\nd\n"},
5821 {{L"string", L"split", L"", L"", 0}, STATUS_CMD_ERROR, L"\n"},
5822 {{L"string", L"split", L"", L"a", 0}, STATUS_CMD_ERROR, L"a\n"},
5823 {{L"string", L"split", L"", L"ab", 0}, STATUS_CMD_OK, L"a\nb\n"},
5824 {{L"string", L"split", L"", L"abc", 0}, STATUS_CMD_OK, L"a\nb\nc\n"},
5825 {{L"string", L"split", L"-m1", L"", L"abc", 0}, STATUS_CMD_OK, L"a\nbc\n"},
5826 {{L"string", L"split", L"-r", L"", L"", 0}, STATUS_CMD_ERROR, L"\n"},
5827 {{L"string", L"split", L"-r", L"", L"a", 0}, STATUS_CMD_ERROR, L"a\n"},
5828 {{L"string", L"split", L"-r", L"", L"ab", 0}, STATUS_CMD_OK, L"a\nb\n"},
5829 {{L"string", L"split", L"-r", L"", L"abc", 0}, STATUS_CMD_OK, L"a\nb\nc\n"},
5830 {{L"string", L"split", L"-r", L"-m1", L"", L"abc", 0}, STATUS_CMD_OK, L"ab\nc\n"},
5831 {{L"string", L"split", L"-q", 0}, STATUS_INVALID_ARGS, L""},
5832 {{L"string", L"split", L"-q", L":", 0}, STATUS_CMD_ERROR, L""},
5833 {{L"string", L"split", L"-q", L"x", L"axbxc", 0}, STATUS_CMD_OK, L""},
5834
5835 {{L"string", L"sub", 0}, STATUS_CMD_ERROR, L""},
5836 {{L"string", L"sub", L"abcde", 0}, STATUS_CMD_OK, L"abcde\n"},
5837 {{L"string", L"sub", L"-l", L"x", L"abcde", 0}, STATUS_INVALID_ARGS, L""},
5838 {{L"string", L"sub", L"-s", L"x", L"abcde", 0}, STATUS_INVALID_ARGS, L""},
5839 {{L"string", L"sub", L"-l0", L"abcde", 0}, STATUS_CMD_OK, L"\n"},
5840 {{L"string", L"sub", L"-l2", L"abcde", 0}, STATUS_CMD_OK, L"ab\n"},
5841 {{L"string", L"sub", L"-l5", L"abcde", 0}, STATUS_CMD_OK, L"abcde\n"},
5842 {{L"string", L"sub", L"-l6", L"abcde", 0}, STATUS_CMD_OK, L"abcde\n"},
5843 {{L"string", L"sub", L"-l-1", L"abcde", 0}, STATUS_INVALID_ARGS, L""},
5844 {{L"string", L"sub", L"-s0", L"abcde", 0}, STATUS_INVALID_ARGS, L""},
5845 {{L"string", L"sub", L"-s1", L"abcde", 0}, STATUS_CMD_OK, L"abcde\n"},
5846 {{L"string", L"sub", L"-s5", L"abcde", 0}, STATUS_CMD_OK, L"e\n"},
5847 {{L"string", L"sub", L"-s6", L"abcde", 0}, STATUS_CMD_OK, L"\n"},
5848 {{L"string", L"sub", L"-s-1", L"abcde", 0}, STATUS_CMD_OK, L"e\n"},
5849 {{L"string", L"sub", L"-s-5", L"abcde", 0}, STATUS_CMD_OK, L"abcde\n"},
5850 {{L"string", L"sub", L"-s-6", L"abcde", 0}, STATUS_CMD_OK, L"abcde\n"},
5851 {{L"string", L"sub", L"-s1", L"-l0", L"abcde", 0}, STATUS_CMD_OK, L"\n"},
5852 {{L"string", L"sub", L"-s1", L"-l1", L"abcde", 0}, STATUS_CMD_OK, L"a\n"},
5853 {{L"string", L"sub", L"-s2", L"-l2", L"abcde", 0}, STATUS_CMD_OK, L"bc\n"},
5854 {{L"string", L"sub", L"-s-1", L"-l1", L"abcde", 0}, STATUS_CMD_OK, L"e\n"},
5855 {{L"string", L"sub", L"-s-1", L"-l2", L"abcde", 0}, STATUS_CMD_OK, L"e\n"},
5856 {{L"string", L"sub", L"-s-3", L"-l2", L"abcde", 0}, STATUS_CMD_OK, L"cd\n"},
5857 {{L"string", L"sub", L"-s-3", L"-l4", L"abcde", 0}, STATUS_CMD_OK, L"cde\n"},
5858 {{L"string", L"sub", L"-q", 0}, STATUS_CMD_ERROR, L""},
5859 {{L"string", L"sub", L"-q", L"abcde", 0}, STATUS_CMD_OK, L""},
5860
5861 {{L"string", L"trim", 0}, STATUS_CMD_ERROR, L""},
5862 {{L"string", L"trim", L""}, STATUS_CMD_ERROR, L"\n"},
5863 {{L"string", L"trim", L" "}, STATUS_CMD_OK, L"\n"},
5864 {{L"string", L"trim", L" \f\n\r\t"}, STATUS_CMD_OK, L"\n"},
5865 {{L"string", L"trim", L" a"}, STATUS_CMD_OK, L"a\n"},
5866 {{L"string", L"trim", L"a "}, STATUS_CMD_OK, L"a\n"},
5867 {{L"string", L"trim", L" a "}, STATUS_CMD_OK, L"a\n"},
5868 {{L"string", L"trim", L"-l", L" a"}, STATUS_CMD_OK, L"a\n"},
5869 {{L"string", L"trim", L"-l", L"a "}, STATUS_CMD_ERROR, L"a \n"},
5870 {{L"string", L"trim", L"-l", L" a "}, STATUS_CMD_OK, L"a \n"},
5871 {{L"string", L"trim", L"-r", L" a"}, STATUS_CMD_ERROR, L" a\n"},
5872 {{L"string", L"trim", L"-r", L"a "}, STATUS_CMD_OK, L"a\n"},
5873 {{L"string", L"trim", L"-r", L" a "}, STATUS_CMD_OK, L" a\n"},
5874 {{L"string", L"trim", L"-c", L".", L" a"}, STATUS_CMD_ERROR, L" a\n"},
5875 {{L"string", L"trim", L"-c", L".", L"a "}, STATUS_CMD_ERROR, L"a \n"},
5876 {{L"string", L"trim", L"-c", L".", L" a "}, STATUS_CMD_ERROR, L" a \n"},
5877 {{L"string", L"trim", L"-c", L".", L".a"}, STATUS_CMD_OK, L"a\n"},
5878 {{L"string", L"trim", L"-c", L".", L"a."}, STATUS_CMD_OK, L"a\n"},
5879 {{L"string", L"trim", L"-c", L".", L".a."}, STATUS_CMD_OK, L"a\n"},
5880 {{L"string", L"trim", L"-c", L"\\/", L"/a\\"}, STATUS_CMD_OK, L"a\n"},
5881 {{L"string", L"trim", L"-c", L"\\/", L"a/"}, STATUS_CMD_OK, L"a\n"},
5882 {{L"string", L"trim", L"-c", L"\\/", L"\\a/"}, STATUS_CMD_OK, L"a\n"},
5883 {{L"string", L"trim", L"-c", L"", L".a."}, STATUS_CMD_ERROR, L".a.\n"}};
5884
5885 for (const auto &t : string_tests) {
5886 run_one_string_test(t.argv, t.expected_rc, t.expected_out);
5887 }
5888
5889 const auto saved_flags = fish_features();
5890 const struct string_test qmark_noglob_tests[] = {
5891 {{L"string", L"match", L"a*b?c", L"axxb?c", 0}, STATUS_CMD_OK, L"axxb?c\n"},
5892 {{L"string", L"match", L"*?", L"a", 0}, STATUS_CMD_ERROR, L""},
5893 {{L"string", L"match", L"*?", L"ab", 0}, STATUS_CMD_ERROR, L""},
5894 {{L"string", L"match", L"?*", L"a", 0}, STATUS_CMD_ERROR, L""},
5895 {{L"string", L"match", L"?*", L"ab", 0}, STATUS_CMD_ERROR, L""},
5896 {{L"string", L"match", L"a*\\?", L"abc?", 0}, STATUS_CMD_ERROR, L""}};
5897 mutable_fish_features().set(features_t::qmark_noglob, true);
5898 for (const auto &t : qmark_noglob_tests) {
5899 run_one_string_test(t.argv, t.expected_rc, t.expected_out);
5900 }
5901
5902 const struct string_test qmark_glob_tests[] = {
5903 {{L"string", L"match", L"a*b?c", L"axxbyc", 0}, STATUS_CMD_OK, L"axxbyc\n"},
5904 {{L"string", L"match", L"*?", L"a", 0}, STATUS_CMD_OK, L"a\n"},
5905 {{L"string", L"match", L"*?", L"ab", 0}, STATUS_CMD_OK, L"ab\n"},
5906 {{L"string", L"match", L"?*", L"a", 0}, STATUS_CMD_OK, L"a\n"},
5907 {{L"string", L"match", L"?*", L"ab", 0}, STATUS_CMD_OK, L"ab\n"},
5908 {{L"string", L"match", L"a*\\?", L"abc?", 0}, STATUS_CMD_OK, L"abc?\n"}};
5909 mutable_fish_features().set(features_t::qmark_noglob, false);
5910 for (const auto &t : qmark_glob_tests) {
5911 run_one_string_test(t.argv, t.expected_rc, t.expected_out);
5912 }
5913 mutable_fish_features() = saved_flags;
5914 }
5915
5916 /// Helper for test_timezone_env_vars().
return_timezone_hour(time_t tstamp,const wchar_t * timezone)5917 long return_timezone_hour(time_t tstamp, const wchar_t *timezone) {
5918 auto &vars = parser_t::principal_parser().vars();
5919 struct tm ltime;
5920 char ltime_str[3];
5921 char *str_ptr;
5922 size_t n;
5923
5924 vars.set_one(L"TZ", ENV_EXPORT, timezone);
5925
5926 const auto var = vars.get(L"TZ", ENV_DEFAULT);
5927 (void)var;
5928
5929 localtime_r(&tstamp, <ime);
5930 n = strftime(ltime_str, 3, "%H", <ime);
5931 if (n != 2) {
5932 err(L"strftime() returned %d, expected 2", n);
5933 return 0;
5934 }
5935 return strtol(ltime_str, &str_ptr, 10);
5936 }
5937
5938 /// Verify that setting special env vars have the expected effect on the current shell process.
test_timezone_env_vars()5939 static void test_timezone_env_vars() {
5940 // Confirm changing the timezone affects fish's idea of the local time.
5941 time_t tstamp = time(NULL);
5942
5943 long first_tstamp = return_timezone_hour(tstamp, L"UTC-1");
5944 long second_tstamp = return_timezone_hour(tstamp, L"UTC-2");
5945 long delta = second_tstamp - first_tstamp;
5946 if (delta != 1 && delta != -23) {
5947 err(L"expected a one hour timezone delta got %ld", delta);
5948 }
5949 }
5950
5951 /// Verify that setting special env vars have the expected effect on the current shell process.
test_env_vars()5952 static void test_env_vars() {
5953 test_timezone_env_vars();
5954 // TODO: Add tests for the locale and ncurses vars.
5955
5956 env_var_t v1 = {L"abc", env_var_t::flag_export};
5957 env_var_t v2 = {wcstring_list_t{L"abc"}, env_var_t::flag_export};
5958 env_var_t v3 = {wcstring_list_t{L"abc"}, 0};
5959 env_var_t v4 = {wcstring_list_t{L"abc", L"def"}, env_var_t::flag_export};
5960 do_test(v1 == v2 && !(v1 != v2));
5961 do_test(v1 != v3 && !(v1 == v3));
5962 do_test(v1 != v4 && !(v1 == v4));
5963 }
5964
test_env_snapshot()5965 static void test_env_snapshot() {
5966 if (system("mkdir -p test/fish_env_snapshot_test/")) err(L"mkdir failed");
5967 bool pushed = pushd("test/fish_env_snapshot_test");
5968 do_test(pushed);
5969 auto &vars = parser_t::principal_parser().vars();
5970 vars.push(true);
5971 wcstring before_pwd = vars.get(L"PWD")->as_string();
5972 vars.set(L"test_env_snapshot_var", 0, {L"before"});
5973 const auto snapshot = vars.snapshot();
5974 vars.set(L"PWD", 0, {L"/newdir"});
5975 vars.set(L"test_env_snapshot_var", 0, {L"after"});
5976 vars.set(L"test_env_snapshot_var_2", 0, {L"after"});
5977
5978 // vars should be unaffected by the snapshot
5979 do_test(vars.get(L"PWD")->as_string() == L"/newdir");
5980 do_test(vars.get(L"test_env_snapshot_var")->as_string() == L"after");
5981 do_test(vars.get(L"test_env_snapshot_var_2")->as_string() == L"after");
5982
5983 // snapshot should have old values of vars
5984 do_test(snapshot->get(L"PWD")->as_string() == before_pwd);
5985 do_test(snapshot->get(L"test_env_snapshot_var")->as_string() == L"before");
5986 do_test(snapshot->get(L"test_env_snapshot_var_2") == none());
5987
5988 // snapshots see global var changes except for perproc like PWD
5989 vars.set(L"test_env_snapshot_var_3", ENV_GLOBAL, {L"reallyglobal"});
5990 do_test(vars.get(L"test_env_snapshot_var_3")->as_string() == L"reallyglobal");
5991 do_test(snapshot->get(L"test_env_snapshot_var_3")->as_string() == L"reallyglobal");
5992
5993 vars.pop();
5994 popd();
5995 }
5996
test_illegal_command_exit_code()5997 static void test_illegal_command_exit_code() {
5998 say(L"Testing illegal command exit code");
5999
6000 // We need to be in an empty directory so that none of the wildcards match a file that might be
6001 // in the fish source tree. In particular we need to ensure that "?" doesn't match a file
6002 // named by a single character. See issue #3852.
6003 if (!pushd("test/temp")) return;
6004
6005 struct command_result_tuple_t {
6006 const wchar_t *txt;
6007 int result;
6008 };
6009
6010 const command_result_tuple_t tests[] = {
6011 {L"echo -n", STATUS_CMD_OK},
6012 {L"pwd", STATUS_CMD_OK},
6013 {L"UNMATCHABLE_WILDCARD*", STATUS_UNMATCHED_WILDCARD},
6014 {L"UNMATCHABLE_WILDCARD**", STATUS_UNMATCHED_WILDCARD},
6015 {L"?", STATUS_UNMATCHED_WILDCARD},
6016 {L"abc?def", STATUS_UNMATCHED_WILDCARD},
6017 };
6018
6019 const io_chain_t empty_ios;
6020 parser_t &parser = parser_t::principal_parser();
6021
6022 for (const auto &test : tests) {
6023 parser.eval(test.txt, empty_ios);
6024
6025 int exit_status = parser.get_last_status();
6026 if (exit_status != test.result) {
6027 err(L"command '%ls': expected exit code %d, got %d", test.txt, test.result,
6028 exit_status);
6029 }
6030 }
6031
6032 popd();
6033 }
6034
test_maybe()6035 void test_maybe() {
6036 say(L"Testing maybe_t");
6037 do_test(!bool(maybe_t<int>()));
6038 maybe_t<int> m(3);
6039 do_test(m.has_value());
6040 do_test(m.value() == 3);
6041 m.reset();
6042 do_test(!m.has_value());
6043 m = 123;
6044 do_test(m.has_value());
6045 do_test(m.has_value() && m.value() == 123);
6046 m = maybe_t<int>();
6047 do_test(!m.has_value());
6048 m = maybe_t<int>(64);
6049 do_test(m.has_value() && m.value() == 64);
6050 m = 123;
6051 do_test(m == maybe_t<int>(123));
6052 do_test(m != maybe_t<int>());
6053 do_test(maybe_t<int>() == none());
6054 do_test(!maybe_t<int>(none()).has_value());
6055 m = none();
6056 do_test(!bool(m));
6057
6058 maybe_t<std::string> m2("abc");
6059 do_test(!m2.missing_or_empty());
6060 m2 = "";
6061 do_test(m2.missing_or_empty());
6062 m2 = none();
6063 do_test(m2.missing_or_empty());
6064
6065 maybe_t<std::string> m0 = none();
6066 maybe_t<std::string> m3("hi");
6067 maybe_t<std::string> m4 = m3;
6068 do_test(m4 && *m4 == "hi");
6069 maybe_t<std::string> m5 = m0;
6070 do_test(!m5);
6071
6072 maybe_t<std::string> acquire_test("def");
6073 do_test(acquire_test);
6074 std::string res = acquire_test.acquire();
6075 do_test(!acquire_test);
6076 do_test(res == "def");
6077
6078 // maybe_t<T> should be copyable iff T is copyable.
6079 using copyable = std::shared_ptr<int>;
6080 using noncopyable = std::unique_ptr<int>;
6081 do_test(std::is_copy_assignable<maybe_t<copyable>>::value == true);
6082 do_test(std::is_copy_constructible<maybe_t<copyable>>::value == true);
6083 do_test(std::is_copy_assignable<maybe_t<noncopyable>>::value == false);
6084 do_test(std::is_copy_constructible<maybe_t<noncopyable>>::value == false);
6085
6086 maybe_t<std::string> c1{"abc"};
6087 maybe_t<std::string> c2 = c1;
6088 do_test(c1.value() == "abc");
6089 do_test(c2.value() == "abc");
6090 c2 = c1;
6091 do_test(c1.value() == "abc");
6092 do_test(c2.value() == "abc");
6093 }
6094
test_layout_cache()6095 void test_layout_cache() {
6096 layout_cache_t seqs;
6097
6098 // Verify escape code cache.
6099 do_test(seqs.find_escape_code(L"abc") == 0);
6100 seqs.add_escape_code(L"abc");
6101 seqs.add_escape_code(L"abc");
6102 do_test(seqs.esc_cache_size() == 1);
6103 do_test(seqs.find_escape_code(L"abc") == 3);
6104 do_test(seqs.find_escape_code(L"abcd") == 3);
6105 do_test(seqs.find_escape_code(L"abcde") == 3);
6106 do_test(seqs.find_escape_code(L"xabcde") == 0);
6107 seqs.add_escape_code(L"ac");
6108 do_test(seqs.find_escape_code(L"abcd") == 3);
6109 do_test(seqs.find_escape_code(L"acbd") == 2);
6110 seqs.add_escape_code(L"wxyz");
6111 do_test(seqs.find_escape_code(L"abc") == 3);
6112 do_test(seqs.find_escape_code(L"abcd") == 3);
6113 do_test(seqs.find_escape_code(L"wxyz123") == 4);
6114 do_test(seqs.find_escape_code(L"qwxyz123") == 0);
6115 do_test(seqs.esc_cache_size() == 3);
6116 seqs.clear();
6117 do_test(seqs.esc_cache_size() == 0);
6118 do_test(seqs.find_escape_code(L"abcd") == 0);
6119
6120 auto huge = std::numeric_limits<size_t>::max();
6121
6122 // Verify prompt layout cache.
6123 for (size_t i = 0; i < layout_cache_t::prompt_cache_max_size; i++) {
6124 wcstring input = std::to_wstring(i);
6125 do_test(!seqs.find_prompt_layout(input));
6126 seqs.add_prompt_layout({input, huge, input, {{}, i, 0}});
6127 do_test(seqs.find_prompt_layout(input)->layout.max_line_width == i);
6128 }
6129
6130 size_t expected_evictee = 3;
6131 for (size_t i = 0; i < layout_cache_t::prompt_cache_max_size; i++) {
6132 if (i != expected_evictee)
6133 do_test(seqs.find_prompt_layout(std::to_wstring(i))->layout.max_line_width == i);
6134 }
6135
6136 seqs.add_prompt_layout({L"whatever", huge, L"whatever", {{}, 100, 0}});
6137 do_test(!seqs.find_prompt_layout(std::to_wstring(expected_evictee)));
6138 do_test(seqs.find_prompt_layout(L"whatever", huge)->layout.max_line_width == 100);
6139 }
6140
test_prompt_truncation()6141 void test_prompt_truncation() {
6142 layout_cache_t cache;
6143 wcstring trunc;
6144 prompt_layout_t layout;
6145
6146 /// Helper to return 'layout' formatted as a string for easy comparison.
6147 auto format_layout = [&] {
6148 wcstring line_breaks = L"";
6149 bool first = true;
6150 for (const size_t line_break : layout.line_breaks) {
6151 if (!first) {
6152 line_breaks.push_back(L',');
6153 }
6154 line_breaks.append(format_string(L"%lu", (unsigned long)line_break));
6155 first = false;
6156 }
6157 return format_string(L"[%ls],%lu,%lu", line_breaks.c_str(),
6158 (unsigned long)layout.max_line_width,
6159 (unsigned long)layout.last_line_width);
6160 };
6161
6162 /// Join some strings with newline.
6163 auto join = [](std::initializer_list<wcstring> vals) { return join_strings(vals, L'\n'); };
6164
6165 wcstring ellipsis = {get_ellipsis_char()};
6166
6167 // No truncation.
6168 layout = cache.calc_prompt_layout(L"abcd", &trunc);
6169 do_test(format_layout() == L"[],4,4");
6170 do_test(trunc == L"abcd");
6171
6172 // Line break calculation.
6173 layout = cache.calc_prompt_layout(join({
6174 L"0123456789ABCDEF", //
6175 L"012345", //
6176 L"0123456789abcdef", //
6177 L"xyz" //
6178 }),
6179 &trunc, 80);
6180 do_test(format_layout() == L"[16,23,40],16,3");
6181
6182 // Basic truncation.
6183 layout = cache.calc_prompt_layout(L"0123456789ABCDEF", &trunc, 8);
6184 do_test(format_layout() == L"[],8,8");
6185 do_test(trunc == ellipsis + L"9ABCDEF");
6186
6187 // Multiline truncation.
6188 layout = cache.calc_prompt_layout(join({
6189 L"0123456789ABCDEF", //
6190 L"012345", //
6191 L"0123456789abcdef", //
6192 L"xyz" //
6193 }),
6194 &trunc, 8);
6195 do_test(format_layout() == L"[8,15,24],8,3");
6196 do_test(trunc == join({ellipsis + L"9ABCDEF", L"012345", ellipsis + L"9abcdef", L"xyz"}));
6197
6198 // Escape sequences are not truncated.
6199 layout =
6200 cache.calc_prompt_layout(L"\x1B]50;CurrentDir=test/foo\x07NOT_PART_OF_SEQUENCE", &trunc, 4);
6201 do_test(format_layout() == L"[],4,4");
6202 do_test(trunc == ellipsis + L"\x1B]50;CurrentDir=test/foo\x07NCE");
6203
6204 // Newlines in escape sequences are skipped.
6205 layout = cache.calc_prompt_layout(L"\x1B]50;CurrentDir=\ntest/foo\x07NOT_PART_OF_SEQUENCE",
6206 &trunc, 4);
6207 do_test(format_layout() == L"[],4,4");
6208 do_test(trunc == ellipsis + L"\x1B]50;CurrentDir=\ntest/foo\x07NCE");
6209
6210 // We will truncate down to one character if we have to.
6211 layout = cache.calc_prompt_layout(L"Yay", &trunc, 1);
6212 do_test(format_layout() == L"[],1,1");
6213 do_test(trunc == ellipsis);
6214 }
6215
test_normalize_path()6216 void test_normalize_path() {
6217 say(L"Testing path normalization");
6218 do_test(normalize_path(L"") == L".");
6219 do_test(normalize_path(L"..") == L"..");
6220 do_test(normalize_path(L"./") == L".");
6221 do_test(normalize_path(L"./.") == L".");
6222 do_test(normalize_path(L"/") == L"/");
6223 do_test(normalize_path(L"//") == L"//");
6224 do_test(normalize_path(L"///") == L"/");
6225 do_test(normalize_path(L"////") == L"/");
6226 do_test(normalize_path(L"/.///") == L"/");
6227 do_test(normalize_path(L".//") == L".");
6228 do_test(normalize_path(L"/.//../") == L"/");
6229 do_test(normalize_path(L"////abc") == L"/abc");
6230 do_test(normalize_path(L"/abc") == L"/abc");
6231 do_test(normalize_path(L"/abc/") == L"/abc");
6232 do_test(normalize_path(L"/abc/..def/") == L"/abc/..def");
6233 do_test(normalize_path(L"//abc/../def/") == L"//def");
6234 do_test(normalize_path(L"abc/../abc/../abc/../abc") == L"abc");
6235 do_test(normalize_path(L"../../") == L"../..");
6236 do_test(normalize_path(L"foo/./bar") == L"foo/bar");
6237 do_test(normalize_path(L"foo/../") == L".");
6238 do_test(normalize_path(L"foo/../foo") == L"foo");
6239 do_test(normalize_path(L"foo/../foo/") == L"foo");
6240 do_test(normalize_path(L"foo/././bar/.././baz") == L"foo/baz");
6241
6242 do_test(path_normalize_for_cd(L"/", L"..") == L"/..");
6243 do_test(path_normalize_for_cd(L"/abc/", L"..") == L"/");
6244 do_test(path_normalize_for_cd(L"/abc/def/", L"..") == L"/abc");
6245 do_test(path_normalize_for_cd(L"/abc/def/", L"../..") == L"/");
6246 do_test(path_normalize_for_cd(L"/abc///def/", L"../..") == L"/");
6247 do_test(path_normalize_for_cd(L"/abc///def/", L"../..") == L"/");
6248 do_test(path_normalize_for_cd(L"/abc///def///", L"../..") == L"/");
6249 do_test(path_normalize_for_cd(L"/abc///def///", L"..") == L"/abc");
6250 do_test(path_normalize_for_cd(L"/abc///def///", L"..") == L"/abc");
6251 do_test(path_normalize_for_cd(L"/abc/def/", L"./././/..") == L"/abc");
6252 do_test(path_normalize_for_cd(L"/abc/def/", L"../../../") == L"/../");
6253 do_test(path_normalize_for_cd(L"/abc/def/", L"../ghi/..") == L"/abc/ghi/..");
6254 }
6255
test_dirname_basename()6256 void test_dirname_basename() {
6257 say(L"Testing wdirname and wbasename");
6258 const struct testcase_t {
6259 const wchar_t *path;
6260 const wchar_t *dir;
6261 const wchar_t *base;
6262 } testcases[] = {
6263 {L"", L".", L"."},
6264 {L"foo//", L".", L"foo"},
6265 {L"foo//////", L".", L"foo"},
6266 {L"/////foo", L"/", L"foo"},
6267 {L"/////foo", L"/", L"foo"},
6268 {L"//foo/////bar", L"//foo", L"bar"},
6269 {L"foo/////bar", L"foo", L"bar"},
6270
6271 // Examples given in XPG4.2.
6272 {L"/usr/lib", L"/usr", L"lib"},
6273 {L"usr", L".", L"usr"},
6274 {L"/", L"/", L"/"},
6275 {L".", L".", L"."},
6276 {L"..", L".", L".."},
6277 };
6278 for (const auto &tc : testcases) {
6279 wcstring dir = wdirname(tc.path);
6280 if (dir != tc.dir) {
6281 err(L"Wrong dirname for \"%ls\": expected \"%ls\", got \"%ls\"", tc.path, tc.dir,
6282 dir.c_str());
6283 }
6284 wcstring base = wbasename(tc.path);
6285 if (base != tc.base) {
6286 err(L"Wrong basename for \"%ls\": expected \"%ls\", got \"%ls\"", tc.path, tc.base,
6287 base.c_str());
6288 }
6289 }
6290 // Ensures strings which greatly exceed PATH_MAX still work (#7837).
6291 wcstring longpath = get_overlong_path();
6292 wcstring longpath_dir = longpath.substr(0, longpath.rfind(L'/'));
6293 do_test(wdirname(longpath) == longpath_dir);
6294 do_test(wbasename(longpath) == L"overlong");
6295 }
6296
test_topic_monitor()6297 static void test_topic_monitor() {
6298 say(L"Testing topic monitor");
6299 topic_monitor_t monitor;
6300 generation_list_t gens{};
6301 constexpr auto t = topic_t::sigchld;
6302 gens.sigchld = 0;
6303 do_test(monitor.generation_for_topic(t) == 0);
6304 auto changed = monitor.check(&gens, false /* wait */);
6305 do_test(!changed);
6306 do_test(gens.sigchld == 0);
6307
6308 monitor.post(t);
6309 changed = monitor.check(&gens, true /* wait */);
6310 do_test(changed);
6311 do_test(gens.at(t) == 1);
6312 do_test(monitor.generation_for_topic(t) == 1);
6313
6314 monitor.post(t);
6315 do_test(monitor.generation_for_topic(t) == 2);
6316 changed = monitor.check(&gens, true /* wait */);
6317 do_test(changed);
6318 do_test(gens.sigchld == 2);
6319 }
6320
test_topic_monitor_torture()6321 static void test_topic_monitor_torture() {
6322 say(L"Torture-testing topic monitor");
6323 topic_monitor_t monitor;
6324 const size_t thread_count = 64;
6325 constexpr auto t1 = topic_t::sigchld;
6326 constexpr auto t2 = topic_t::sighupint;
6327 std::vector<generation_list_t> gens;
6328 gens.resize(thread_count, generation_list_t::invalids());
6329 std::atomic<uint32_t> post_count{};
6330 for (auto &gen : gens) {
6331 gen = monitor.current_generations();
6332 post_count += 1;
6333 monitor.post(t1);
6334 }
6335
6336 std::atomic<uint32_t> completed{};
6337 std::vector<std::thread> threads;
6338 for (size_t i = 0; i < thread_count; i++) {
6339 threads.emplace_back(
6340 [&](size_t i) {
6341 for (size_t j = 0; j < (1 << 11); j++) {
6342 auto before = gens[i];
6343 auto changed = monitor.check(&gens[i], true /* wait */);
6344 (void)changed;
6345 do_test(before.at(t1) < gens[i].at(t1));
6346 do_test(gens[i].at(t1) <= post_count);
6347 do_test(gens[i].at(t2) == 0);
6348 }
6349 auto amt = completed.fetch_add(1, std::memory_order_relaxed);
6350 (void)amt;
6351 },
6352 i);
6353 }
6354
6355 while (completed.load(std::memory_order_relaxed) < thread_count) {
6356 post_count += 1;
6357 monitor.post(t1);
6358 std::this_thread::yield();
6359 }
6360 for (auto &t : threads) t.join();
6361 }
6362
test_pipes()6363 static void test_pipes() {
6364 say(L"Testing pipes");
6365 // Here we just test that each pipe has CLOEXEC set and is in the high range.
6366 // Note pipe creation may fail due to fd exhaustion; don't fail in that case.
6367 std::vector<autoclose_pipes_t> pipes;
6368 for (int i = 0; i < 10; i++) {
6369 if (auto pipe = make_autoclose_pipes()) {
6370 pipes.push_back(pipe.acquire());
6371 }
6372 }
6373 for (const auto &pipe : pipes) {
6374 for (int fd : {pipe.read.fd(), pipe.write.fd()}) {
6375 do_test(fd >= k_first_high_fd);
6376 int flags = fcntl(fd, F_GETFD, 0);
6377 do_test(flags >= 0);
6378 do_test(bool(flags & FD_CLOEXEC));
6379 }
6380 }
6381 }
6382
test_fd_event_signaller()6383 static void test_fd_event_signaller() {
6384 say(L"Testing fd event signaller");
6385 fd_event_signaller_t sema;
6386 do_test(!sema.try_consume());
6387 do_test(!sema.poll());
6388
6389 // Post once.
6390 sema.post();
6391 do_test(sema.poll());
6392 do_test(sema.poll());
6393 do_test(sema.try_consume());
6394 do_test(!sema.poll());
6395 do_test(!sema.try_consume());
6396
6397 // Posts are coalesced.
6398 sema.post();
6399 sema.post();
6400 sema.post();
6401 do_test(sema.poll());
6402 do_test(sema.poll());
6403 do_test(sema.try_consume());
6404 do_test(!sema.poll());
6405 do_test(!sema.try_consume());
6406 }
6407
test_timer_format()6408 static void test_timer_format() {
6409 say(L"Testing timer format");
6410 // This test uses numeric output, so we need to set the locale.
6411 char *saved_locale = strdup(setlocale(LC_NUMERIC, nullptr));
6412 setlocale(LC_NUMERIC, "C");
6413 auto t1 = timer_snapshot_t::take();
6414 t1.cpu_fish.ru_utime.tv_usec = 0;
6415 t1.cpu_fish.ru_stime.tv_usec = 0;
6416 t1.cpu_children.ru_utime.tv_usec = 0;
6417 t1.cpu_children.ru_stime.tv_usec = 0;
6418 auto t2 = t1;
6419 t2.cpu_fish.ru_utime.tv_usec = 999995;
6420 t2.cpu_fish.ru_stime.tv_usec = 999994;
6421 t2.cpu_children.ru_utime.tv_usec = 1000;
6422 t2.cpu_children.ru_stime.tv_usec = 500;
6423 t2.wall += std::chrono::microseconds(500);
6424 auto expected =
6425 LR"(
6426 ________________________________________________________
6427 Executed in 500.00 micros fish external
6428 usr time 1.00 secs 1.00 secs 1.00 millis
6429 sys time 1.00 secs 1.00 secs 0.50 millis
6430 )"; // (a) (b) (c)
6431 // (a) remaining columns should align even if there are different units
6432 // (b) carry to the next unit when it would overflow %6.2F
6433 // (c) carry to the next unit when the larger one exceeds 1000
6434 std::wstring actual = timer_snapshot_t::print_delta(t1, t2, true);
6435 if (actual != expected) {
6436 err(L"Failed to format timer snapshot\nExpected: %ls\nActual:%ls\n", expected,
6437 actual.c_str());
6438 }
6439 setlocale(LC_NUMERIC, saved_locale);
6440 free(saved_locale);
6441 }
6442
test_killring()6443 static void test_killring() {
6444 say(L"Testing killring");
6445
6446 do_test(kill_entries().empty());
6447
6448 kill_add(L"a");
6449 kill_add(L"b");
6450 kill_add(L"c");
6451
6452 do_test((kill_entries() == wcstring_list_t{L"c", L"b", L"a"}));
6453
6454 do_test(kill_yank_rotate() == L"b");
6455 do_test((kill_entries() == wcstring_list_t{L"b", L"a", L"c"}));
6456
6457 do_test(kill_yank_rotate() == L"a");
6458 do_test((kill_entries() == wcstring_list_t{L"a", L"c", L"b"}));
6459
6460 kill_add(L"d");
6461
6462 do_test((kill_entries() == wcstring_list_t{L"d", L"a", L"c", L"b"}));
6463
6464 do_test(kill_yank_rotate() == L"a");
6465 do_test((kill_entries() == wcstring_list_t{L"a", L"c", L"b", L"d"}));
6466 }
6467
6468 struct termsize_tester_t {
6469 static void test();
6470 };
6471
test()6472 void termsize_tester_t::test() {
6473 say(L"Testing termsize");
6474
6475 parser_t &parser = parser_t::principal_parser();
6476 env_stack_t &vars = parser.vars();
6477
6478 // Use a static variable so we can pretend we're the kernel exposing a terminal size.
6479 static maybe_t<termsize_t> stubby_termsize{};
6480 termsize_container_t ts([] { return stubby_termsize; });
6481
6482 // Initially default value.
6483 do_test(ts.last() == termsize_t::defaults());
6484
6485 // Haha we change the value, it doesn't even know.
6486 stubby_termsize = termsize_t{42, 84};
6487 do_test(ts.last() == termsize_t::defaults());
6488
6489 // Ok let's tell it. But it still doesn't update right away.
6490 ts.handle_winch();
6491 do_test(ts.last() == termsize_t::defaults());
6492
6493 // Ok now we tell it to update.
6494 ts.updating(parser);
6495 do_test(ts.last() == *stubby_termsize);
6496 do_test(vars.get(L"COLUMNS")->as_string() == L"42");
6497 do_test(vars.get(L"LINES")->as_string() == L"84");
6498
6499 // Wow someone set COLUMNS and LINES to a weird value.
6500 // Now the tty's termsize doesn't matter.
6501 vars.set(L"COLUMNS", ENV_GLOBAL, {L"75"});
6502 vars.set(L"LINES", ENV_GLOBAL, {L"150"});
6503 ts.handle_columns_lines_var_change(vars);
6504 do_test(ts.last() == termsize_t(75, 150));
6505 do_test(vars.get(L"COLUMNS")->as_string() == L"75");
6506 do_test(vars.get(L"LINES")->as_string() == L"150");
6507
6508 vars.set(L"COLUMNS", ENV_GLOBAL, {L"33"});
6509 ts.handle_columns_lines_var_change(vars);
6510 do_test(ts.last() == termsize_t(33, 150));
6511
6512 // Oh it got SIGWINCH, now the tty matters again.
6513 ts.handle_winch();
6514 do_test(ts.last() == termsize_t(33, 150));
6515 do_test(ts.updating(parser) == *stubby_termsize);
6516 do_test(vars.get(L"COLUMNS")->as_string() == L"42");
6517 do_test(vars.get(L"LINES")->as_string() == L"84");
6518
6519 // Test initialize().
6520 vars.set(L"COLUMNS", ENV_GLOBAL, {L"83"});
6521 vars.set(L"LINES", ENV_GLOBAL, {L"38"});
6522 ts.initialize(vars);
6523 do_test(ts.last() == termsize_t(83, 38));
6524
6525 // initialize() even beats the tty reader until a sigwinch.
6526 termsize_container_t ts2([] { return stubby_termsize; });
6527 ts.initialize(vars);
6528 ts2.updating(parser);
6529 do_test(ts.last() == termsize_t(83, 38));
6530 ts2.handle_winch();
6531 do_test(ts2.updating(parser) == *stubby_termsize);
6532 }
6533
6534 /// Main test.
main(int argc,char ** argv)6535 int main(int argc, char **argv) {
6536 UNUSED(argc);
6537 setlocale(LC_ALL, "");
6538
6539 // Look for the file tests/test.fish. We expect to run in a directory containing that file.
6540 // If we don't find it, walk up the directory hierarchy until we do, or error.
6541 while (access("./tests/test.fish", F_OK) != 0) {
6542 char wd[PATH_MAX + 1] = {};
6543 if (!getcwd(wd, sizeof wd)) {
6544 perror("getcwd");
6545 exit(-1);
6546 }
6547 if (!std::strcmp(wd, "/")) {
6548 std::fwprintf(
6549 stderr, L"Unable to find 'tests' directory, which should contain file test.fish\n");
6550 exit(EXIT_FAILURE);
6551 }
6552 if (chdir(dirname(wd)) < 0) {
6553 perror("chdir");
6554 }
6555 }
6556
6557 srandom((unsigned int)time(NULL));
6558 configure_thread_assertions_for_testing();
6559
6560 // Set the program name to this sentinel value
6561 // This will prevent some misleading stderr output during the tests
6562 program_name = TESTS_PROGRAM_NAME;
6563 s_arguments = argv + 1;
6564
6565 struct utsname uname_info;
6566 uname(&uname_info);
6567
6568 say(L"Testing low-level functionality");
6569 set_main_thread();
6570 setup_fork_guards();
6571 proc_init();
6572 builtin_init();
6573 env_init();
6574 misc_init();
6575 reader_init();
6576
6577 // Set default signal handlers, so we can ctrl-C out of this.
6578 signal_reset_handlers();
6579
6580 // Set PWD from getcwd - fixes #5599
6581 env_stack_t::principal().set_pwd_from_getcwd();
6582
6583 if (should_test_function("utility_functions")) test_utility_functions();
6584 if (should_test_function("string_split")) test_split_string_tok();
6585 if (should_test_function("wwrite_to_fd")) test_wwrite_to_fd();
6586 if (should_test_function("env_vars")) test_env_vars();
6587 if (should_test_function("env")) test_env_snapshot();
6588 if (should_test_function("str_to_num")) test_str_to_num();
6589 if (should_test_function("enum")) test_enum_set();
6590 if (should_test_function("enum")) test_enum_array();
6591 if (should_test_function("highlighting")) test_highlighting();
6592 if (should_test_function("new_parser_ll2")) test_new_parser_ll2();
6593 if (should_test_function("new_parser_fuzzing"))
6594 test_new_parser_fuzzing(); // fuzzing is expensive
6595 if (should_test_function("new_parser_correctness")) test_new_parser_correctness();
6596 if (should_test_function("new_parser_ad_hoc")) test_new_parser_ad_hoc();
6597 if (should_test_function("new_parser_errors")) test_new_parser_errors();
6598 if (should_test_function("error_messages")) test_error_messages();
6599 if (should_test_function("escape")) test_unescape_sane();
6600 if (should_test_function("escape")) test_escape_crazy();
6601 if (should_test_function("escape")) test_escape_quotes();
6602 if (should_test_function("format")) test_format();
6603 if (should_test_function("convert")) test_convert();
6604 if (should_test_function("convert")) test_convert_private_use();
6605 if (should_test_function("convert_ascii")) test_convert_ascii();
6606 if (should_test_function("perf_convert_ascii", false)) perf_convert_ascii();
6607 if (should_test_function("convert_nulls")) test_convert_nulls();
6608 if (should_test_function("tokenizer")) test_tokenizer();
6609 if (should_test_function("fd_monitor")) test_fd_monitor();
6610 if (should_test_function("iothread")) test_iothread();
6611 if (should_test_function("pthread")) test_pthread();
6612 if (should_test_function("debounce")) test_debounce();
6613 if (should_test_function("debounce")) test_debounce_timeout();
6614 if (should_test_function("parser")) test_parser();
6615 if (should_test_function("cancellation")) test_cancellation();
6616 if (should_test_function("indents")) test_indents();
6617 if (should_test_function("utf8")) test_utf8();
6618 if (should_test_function("feature_flags")) test_feature_flags();
6619 if (should_test_function("escape_sequences")) test_escape_sequences();
6620 if (should_test_function("pcre2_escape")) test_pcre2_escape();
6621 if (should_test_function("lru")) test_lru();
6622 if (should_test_function("expand")) test_expand();
6623 if (should_test_function("expand")) test_expand_overflow();
6624 if (should_test_function("fuzzy_match")) test_fuzzy_match();
6625 if (should_test_function("ifind")) test_ifind();
6626 if (should_test_function("ifind_fuzzy")) test_ifind_fuzzy();
6627 if (should_test_function("abbreviations")) test_abbreviations();
6628 if (should_test_function("test")) test_test();
6629 if (should_test_function("wcstod")) test_wcstod();
6630 if (should_test_function("dup2s")) test_dup2s();
6631 if (should_test_function("dup2s")) test_dup2s_fd_for_target_fd();
6632 if (should_test_function("path")) test_path();
6633 if (should_test_function("pager_navigation")) test_pager_navigation();
6634 if (should_test_function("pager_layout")) test_pager_layout();
6635 if (should_test_function("word_motion")) test_word_motion();
6636 if (should_test_function("is_potential_path")) test_is_potential_path();
6637 if (should_test_function("colors")) test_colors();
6638 if (should_test_function("complete")) test_complete();
6639 if (should_test_function("autoload")) test_autoload();
6640 if (should_test_function("input")) test_input();
6641 if (should_test_function("line_iterator")) test_line_iterator();
6642 if (should_test_function("undo")) test_undo();
6643 if (should_test_function("universal")) test_universal();
6644 if (should_test_function("universal")) test_universal_output();
6645 if (should_test_function("universal")) test_universal_parsing();
6646 if (should_test_function("universal")) test_universal_parsing_legacy();
6647 if (should_test_function("universal")) test_universal_callbacks();
6648 if (should_test_function("universal")) test_universal_formats();
6649 if (should_test_function("universal")) test_universal_ok_to_save();
6650 if (should_test_function("notifiers")) test_universal_notifiers();
6651 if (should_test_function("wait_handles")) test_wait_handles();
6652 if (should_test_function("completion_insertions")) test_completion_insertions();
6653 if (should_test_function("autosuggestion_ignores")) test_autosuggestion_ignores();
6654 if (should_test_function("autosuggestion_combining")) test_autosuggestion_combining();
6655 if (should_test_function("autosuggest_suggest_special")) test_autosuggest_suggest_special();
6656 if (should_test_function("history")) history_tests_t::test_history();
6657 if (should_test_function("history_merge")) history_tests_t::test_history_merge();
6658 if (should_test_function("history_paths")) history_tests_t::test_history_path_detection();
6659 if (!is_windows_subsystem_for_linux()) {
6660 // this test always fails under WSL
6661 if (should_test_function("history_races")) history_tests_t::test_history_races();
6662 }
6663 if (should_test_function("history_formats")) history_tests_t::test_history_formats();
6664 if (should_test_function("string")) test_string();
6665 if (should_test_function("illegal_command_exit_code")) test_illegal_command_exit_code();
6666 if (should_test_function("maybe")) test_maybe();
6667 if (should_test_function("layout_cache")) test_layout_cache();
6668 if (should_test_function("prompt")) test_prompt_truncation();
6669 if (should_test_function("normalize")) test_normalize_path();
6670 if (should_test_function("dirname")) test_dirname_basename();
6671 if (should_test_function("topics")) test_topic_monitor();
6672 if (should_test_function("topics")) test_topic_monitor_torture();
6673 if (should_test_function("pipes")) test_pipes();
6674 if (should_test_function("fd_event")) test_fd_event_signaller();
6675 if (should_test_function("timer_format")) test_timer_format();
6676 // history_tests_t::test_history_speed();
6677
6678 if (should_test_function("termsize")) termsize_tester_t::test();
6679 if (should_test_function("killring")) test_killring();
6680
6681 say(L"Encountered %d errors in low-level tests", err_count);
6682 if (s_test_run_count == 0) say(L"*** No Tests Were Actually Run! ***");
6683
6684 if (err_count != 0) {
6685 return 1;
6686 }
6687 }
6688