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> &note) {
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, &ltime);
5930     n = strftime(ltime_str, 3, "%H", &ltime);
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