2016-05-01 01:37:19 +00:00
|
|
|
// Various bug and feature tests. Compiled and run by make test.
|
2016-05-18 22:30:21 +00:00
|
|
|
#include "config.h" // IWYU pragma: keep
|
2016-05-03 05:20:53 +00:00
|
|
|
|
2019-04-28 18:08:18 +00:00
|
|
|
#include <fcntl.h>
|
2016-05-01 01:37:19 +00:00
|
|
|
#include <libgen.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <stdarg.h>
|
2022-08-21 06:14:48 +00:00
|
|
|
#include <sys/resource.h>
|
2017-04-09 04:05:18 +00:00
|
|
|
#include <sys/stat.h>
|
2016-05-23 02:00:13 +00:00
|
|
|
#include <sys/utsname.h>
|
2016-05-01 01:37:19 +00:00
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
2017-02-11 02:47:02 +00:00
|
|
|
|
2012-01-27 19:43:45 +00:00
|
|
|
#include <algorithm>
|
2017-02-06 19:04:07 +00:00
|
|
|
#include <array>
|
2017-01-30 05:35:22 +00:00
|
|
|
#include <atomic>
|
2022-08-21 06:14:48 +00:00
|
|
|
#include <cerrno>
|
2022-08-21 21:51:33 +00:00
|
|
|
#include <chrono>
|
2022-08-21 06:14:48 +00:00
|
|
|
#include <clocale>
|
2019-01-05 11:31:24 +00:00
|
|
|
#include <cmath>
|
2022-12-09 21:31:19 +00:00
|
|
|
#include <cstdint>
|
|
|
|
#include <cstdio>
|
|
|
|
#include <cstdlib>
|
2019-10-13 22:50:48 +00:00
|
|
|
#include <cstring>
|
|
|
|
#include <cwchar>
|
2017-02-11 02:47:02 +00:00
|
|
|
#include <functional>
|
2022-08-21 06:14:48 +00:00
|
|
|
#include <future>
|
|
|
|
#include <initializer_list>
|
|
|
|
#include <iterator>
|
|
|
|
#include <limits>
|
|
|
|
#include <list>
|
|
|
|
#include <map>
|
2016-04-21 06:00:54 +00:00
|
|
|
#include <memory>
|
2022-08-21 06:14:48 +00:00
|
|
|
#include <mutex>
|
2016-04-21 06:00:54 +00:00
|
|
|
#include <set>
|
2016-05-01 01:37:19 +00:00
|
|
|
#include <string>
|
2019-10-13 22:50:48 +00:00
|
|
|
#include <thread>
|
2022-08-21 06:14:48 +00:00
|
|
|
#include <type_traits>
|
|
|
|
#include <unordered_map>
|
2017-02-11 02:47:02 +00:00
|
|
|
#include <utility>
|
2016-04-21 06:00:54 +00:00
|
|
|
#include <vector>
|
2023-02-24 23:54:46 +00:00
|
|
|
|
2023-02-18 01:21:44 +00:00
|
|
|
#include "fds.rs.h"
|
|
|
|
#include "parse_constants.rs.h"
|
2005-09-20 13:26:39 +00:00
|
|
|
|
2022-10-24 20:54:46 +00:00
|
|
|
#ifdef FISH_CI_SAN
|
|
|
|
#include <sanitizer/lsan_interface.h>
|
|
|
|
#endif
|
|
|
|
|
2022-04-01 19:05:27 +00:00
|
|
|
#include "abbrs.h"
|
2020-07-02 04:06:58 +00:00
|
|
|
#include "ast.h"
|
2019-04-28 18:08:18 +00:00
|
|
|
#include "autoload.h"
|
2005-09-20 13:26:39 +00:00
|
|
|
#include "builtin.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "color.h"
|
|
|
|
#include "common.h"
|
2005-09-20 13:26:39 +00:00
|
|
|
#include "complete.h"
|
2023-01-14 22:56:24 +00:00
|
|
|
#include "cxxgen.h"
|
2022-08-21 06:14:48 +00:00
|
|
|
#include "enum_set.h"
|
2005-09-20 13:26:39 +00:00
|
|
|
#include "env.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "env_universal_common.h"
|
|
|
|
#include "expand.h"
|
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
2023-02-18 01:21:44 +00:00
|
|
|
#include "fd_monitor.rs.h"
|
2023-01-14 22:56:24 +00:00
|
|
|
#include "fd_readable_set.rs.h"
|
2022-08-21 21:51:33 +00:00
|
|
|
#include "fds.h"
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
#include "ffi_baggage.h"
|
2023-01-14 22:56:24 +00:00
|
|
|
#include "ffi_init.rs.h"
|
2023-01-15 22:56:04 +00:00
|
|
|
#include "ffi_tests.rs.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "function.h"
|
2023-02-07 22:18:51 +00:00
|
|
|
#include "future_feature_flags.h"
|
2022-08-21 21:51:33 +00:00
|
|
|
#include "global_safety.h"
|
2012-02-19 07:26:39 +00:00
|
|
|
#include "highlight.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "history.h"
|
2014-02-12 20:49:32 +00:00
|
|
|
#include "input.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "input_common.h"
|
2016-04-21 06:00:54 +00:00
|
|
|
#include "io.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "iothread.h"
|
2021-05-10 19:26:30 +00:00
|
|
|
#include "kill.h"
|
2016-04-21 06:00:54 +00:00
|
|
|
#include "lru.h"
|
2017-08-27 20:19:37 +00:00
|
|
|
#include "maybe.h"
|
2022-08-21 06:14:48 +00:00
|
|
|
#include "null_terminated_array.h"
|
2020-01-16 01:14:47 +00:00
|
|
|
#include "operation_context.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "pager.h"
|
2016-04-21 06:00:54 +00:00
|
|
|
#include "parse_constants.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "parse_tree.h"
|
|
|
|
#include "parse_util.h"
|
|
|
|
#include "parser.h"
|
|
|
|
#include "path.h"
|
|
|
|
#include "proc.h"
|
2022-07-09 18:08:27 +00:00
|
|
|
#include "re.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "reader.h"
|
2019-01-28 21:26:22 +00:00
|
|
|
#include "redirection.h"
|
2016-04-21 06:00:54 +00:00
|
|
|
#include "screen.h"
|
2023-01-14 22:56:24 +00:00
|
|
|
#include "signals.h"
|
|
|
|
#include "smoke.rs.h"
|
2020-06-07 23:05:52 +00:00
|
|
|
#include "termsize.h"
|
2023-04-29 17:07:59 +00:00
|
|
|
#include "threads.rs.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "tokenizer.h"
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
#include "topic_monitor.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "utf8.h"
|
2017-05-13 03:15:24 +00:00
|
|
|
#include "util.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "wcstringutil.h"
|
2022-12-09 21:31:19 +00:00
|
|
|
#include "wgetopt.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "wildcard.h"
|
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2012-07-06 21:34:53 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static const char *const *s_arguments;
|
2013-12-08 21:41:12 +00:00
|
|
|
static int s_test_run_count = 0;
|
2013-10-12 18:32:34 +00:00
|
|
|
|
2020-07-12 19:20:38 +00:00
|
|
|
#define system_assert(command) \
|
|
|
|
if (system(command)) { \
|
|
|
|
err(L"Non-zero result on line %d: %s", __LINE__, command); \
|
|
|
|
}
|
2020-07-05 22:38:32 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Indicate if we should test the given function. Either we test everything (all arguments) or we
|
|
|
|
// run only tests that have a prefix in s_arguments.
|
2020-09-19 21:04:47 +00:00
|
|
|
// If \p default_on is set, then allow no args to run this test by default.
|
|
|
|
static bool should_test_function(const char *func_name, bool default_on = true) {
|
2013-12-08 21:41:12 +00:00
|
|
|
bool result = false;
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!s_arguments || !s_arguments[0]) {
|
2020-09-19 21:04:47 +00:00
|
|
|
// No args, test if defaulted on.
|
|
|
|
result = default_on;
|
2016-05-01 01:37:19 +00:00
|
|
|
} else {
|
2021-08-17 22:57:52 +00:00
|
|
|
for (size_t i = 0; s_arguments[i] != nullptr; i++) {
|
2021-03-23 07:46:31 +00:00
|
|
|
if (!std::strcmp(func_name, s_arguments[i])) {
|
2013-12-08 21:41:12 +00:00
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
2013-10-12 18:32:34 +00:00
|
|
|
}
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
if (result) s_test_run_count++;
|
2013-12-08 21:41:12 +00:00
|
|
|
return result;
|
2013-10-12 18:32:34 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// The number of tests to run.
|
2022-09-20 18:58:37 +00:00
|
|
|
#define ESCAPE_TEST_COUNT 100000
|
2016-05-01 01:37:19 +00:00
|
|
|
/// The average length of strings to unescape.
|
2022-09-20 18:58:37 +00:00
|
|
|
#define ESCAPE_TEST_LENGTH 100
|
2019-10-22 00:21:40 +00:00
|
|
|
/// The highest character number of character to try and escape.
|
2022-09-20 18:58:37 +00:00
|
|
|
#define ESCAPE_TEST_CHAR 4000
|
2005-09-20 13:26:39 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Number of encountered errors.
|
|
|
|
static int err_count = 0;
|
2005-09-20 13:26:39 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Print formatted output.
|
2016-11-23 04:24:03 +00:00
|
|
|
static void say(const wchar_t *fmt, ...) {
|
2012-11-19 00:30:30 +00:00
|
|
|
va_list va;
|
2016-11-23 04:24:03 +00:00
|
|
|
va_start(va, fmt);
|
2019-03-12 21:06:01 +00:00
|
|
|
std::vfwprintf(stdout, fmt, va);
|
2012-11-19 00:30:30 +00:00
|
|
|
va_end(va);
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stdout, L"\n");
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Print formatted error string.
|
|
|
|
static void err(const wchar_t *blah, ...) {
|
2012-11-19 00:30:30 +00:00
|
|
|
va_list va;
|
|
|
|
va_start(va, blah);
|
|
|
|
err_count++;
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Show errors in red.
|
2019-12-18 00:19:12 +00:00
|
|
|
std::fputws(L"\x1B[31m", stdout);
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stdout, L"Error: ");
|
|
|
|
std::vfwprintf(stdout, blah, va);
|
2012-11-19 00:30:30 +00:00
|
|
|
va_end(va);
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Return to normal color.
|
2019-12-18 00:19:12 +00:00
|
|
|
std::fputws(L"\x1B[0m", stdout);
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stdout, L"\n");
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
/// Joins a std::vector<wcstring> via commas.
|
|
|
|
static wcstring comma_join(const std::vector<wcstring> &lst) {
|
2014-08-16 01:14:36 +00:00
|
|
|
wcstring result;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < lst.size(); i++) {
|
|
|
|
if (i > 0) {
|
2014-08-16 01:14:36 +00:00
|
|
|
result.push_back(L',');
|
|
|
|
}
|
|
|
|
result.append(lst.at(i));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-10-11 16:58:16 +00:00
|
|
|
static std::vector<std::string> pushed_dirs;
|
2017-04-09 04:05:18 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Helper to chdir and then update $PWD.
|
2017-04-09 04:05:18 +00:00
|
|
|
static bool pushd(const char *path) {
|
|
|
|
char cwd[PATH_MAX] = {};
|
2021-08-17 22:57:52 +00:00
|
|
|
if (getcwd(cwd, sizeof cwd) == nullptr) {
|
2017-04-09 04:05:18 +00:00
|
|
|
err(L"getcwd() from pushd() failed: errno = %d", errno);
|
|
|
|
return false;
|
|
|
|
}
|
2021-08-17 20:36:52 +00:00
|
|
|
pushed_dirs.emplace_back(cwd);
|
2017-04-09 04:05:18 +00:00
|
|
|
|
|
|
|
// We might need to create the directory. We don't care if this fails due to the directory
|
|
|
|
// already being present.
|
|
|
|
mkdir(path, 0770);
|
|
|
|
|
2016-02-16 22:11:23 +00:00
|
|
|
int ret = chdir(path);
|
2017-04-09 04:05:18 +00:00
|
|
|
if (ret != 0) {
|
|
|
|
err(L"chdir(\"%s\") from pushd() failed: errno = %d", path, errno);
|
|
|
|
return false;
|
2016-02-16 22:11:23 +00:00
|
|
|
}
|
2017-04-09 04:05:18 +00:00
|
|
|
|
2018-09-09 19:17:31 +00:00
|
|
|
env_stack_t::principal().set_pwd_from_getcwd();
|
2017-04-09 04:05:18 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void popd() {
|
2018-10-11 16:58:16 +00:00
|
|
|
const std::string &old_cwd = pushed_dirs.back();
|
|
|
|
if (chdir(old_cwd.c_str()) == -1) {
|
|
|
|
err(L"chdir(\"%s\") from popd() failed: errno = %d", old_cwd.c_str(), errno);
|
2017-04-09 04:05:18 +00:00
|
|
|
}
|
|
|
|
pushed_dirs.pop_back();
|
2018-09-09 19:17:31 +00:00
|
|
|
env_stack_t::principal().set_pwd_from_getcwd();
|
2016-02-16 22:11:23 +00:00
|
|
|
}
|
|
|
|
|
2021-03-21 23:00:29 +00:00
|
|
|
// Helper to return a string whose length greatly exceeds PATH_MAX.
|
|
|
|
wcstring get_overlong_path() {
|
|
|
|
wcstring longpath;
|
|
|
|
longpath.reserve(PATH_MAX * 2 + 10);
|
|
|
|
while (longpath.size() <= PATH_MAX * 2) {
|
|
|
|
longpath += L"/overlong";
|
|
|
|
}
|
|
|
|
return longpath;
|
|
|
|
}
|
|
|
|
|
2016-11-05 01:40:22 +00:00
|
|
|
// The odd formulation of these macros is to avoid "multiple unary operator" warnings from oclint
|
|
|
|
// were we to use the more natural "if (!(e)) err(..." form. We have to do this because the rules
|
|
|
|
// for the C preprocessor make it practically impossible to embed a comment in the body of a macro.
|
2016-11-23 04:24:03 +00:00
|
|
|
#define do_test(e) \
|
|
|
|
do { \
|
|
|
|
if (e) { \
|
|
|
|
; \
|
|
|
|
} else { \
|
|
|
|
err(L"Test failed on line %lu: %s", __LINE__, #e); \
|
|
|
|
} \
|
2016-05-01 01:37:19 +00:00
|
|
|
} while (0)
|
|
|
|
|
2016-11-23 04:24:03 +00:00
|
|
|
#define do_test_from(e, from) \
|
|
|
|
do { \
|
|
|
|
if (e) { \
|
|
|
|
; \
|
|
|
|
} else { \
|
|
|
|
err(L"Test failed on line %lu (from %lu): %s", __LINE__, from, #e); \
|
|
|
|
} \
|
2016-09-24 03:12:15 +00:00
|
|
|
} while (0)
|
|
|
|
|
2016-11-23 04:24:03 +00:00
|
|
|
#define do_test1(e, msg) \
|
|
|
|
do { \
|
|
|
|
if (e) { \
|
|
|
|
; \
|
|
|
|
} else { \
|
|
|
|
err(L"Test failed on line %lu: %ls", __LINE__, (msg)); \
|
|
|
|
} \
|
2016-05-01 01:37:19 +00:00
|
|
|
} while (0)
|
|
|
|
|
2016-11-23 04:24:03 +00:00
|
|
|
/// Test that the fish functions for converting strings to numbers work.
|
|
|
|
static void test_str_to_num() {
|
2017-08-09 18:11:58 +00:00
|
|
|
say(L"Testing str_to_num");
|
2016-11-23 04:24:03 +00:00
|
|
|
const wchar_t *end;
|
|
|
|
int i;
|
|
|
|
long l;
|
|
|
|
|
|
|
|
i = fish_wcstoi(L"");
|
|
|
|
do_test1(errno == EINVAL && i == 0, L"converting empty string to int did not fail");
|
|
|
|
i = fish_wcstoi(L" \n ");
|
|
|
|
do_test1(errno == EINVAL && i == 0, L"converting whitespace string to int did not fail");
|
|
|
|
i = fish_wcstoi(L"123");
|
|
|
|
do_test1(errno == 0 && i == 123, L"converting valid num to int did not succeed");
|
|
|
|
i = fish_wcstoi(L"-123");
|
|
|
|
do_test1(errno == 0 && i == -123, L"converting valid num to int did not succeed");
|
|
|
|
i = fish_wcstoi(L" 345 ");
|
|
|
|
do_test1(errno == 0 && i == 345, L"converting valid num to int did not succeed");
|
|
|
|
i = fish_wcstoi(L" -345 ");
|
|
|
|
do_test1(errno == 0 && i == -345, L"converting valid num to int did not succeed");
|
|
|
|
i = fish_wcstoi(L"x345");
|
|
|
|
do_test1(errno == EINVAL && i == 0, L"converting invalid num to int did not fail");
|
|
|
|
i = fish_wcstoi(L" x345");
|
|
|
|
do_test1(errno == EINVAL && i == 0, L"converting invalid num to int did not fail");
|
|
|
|
i = fish_wcstoi(L"456 x");
|
|
|
|
do_test1(errno == -1 && i == 456, L"converting invalid num to int did not fail");
|
|
|
|
i = fish_wcstoi(L"99999999999999999999999");
|
|
|
|
do_test1(errno == ERANGE && i == INT_MAX, L"converting invalid num to int did not fail");
|
|
|
|
i = fish_wcstoi(L"-99999999999999999999999");
|
|
|
|
do_test1(errno == ERANGE && i == INT_MIN, L"converting invalid num to int did not fail");
|
|
|
|
i = fish_wcstoi(L"567]", &end);
|
|
|
|
do_test1(errno == -1 && i == 567 && *end == L']',
|
|
|
|
L"converting valid num to int did not succeed");
|
|
|
|
// This is subtle. "567" in base 8 is "375" in base 10. The final "8" is not converted.
|
|
|
|
i = fish_wcstoi(L"5678", &end, 8);
|
|
|
|
do_test1(errno == -1 && i == 375 && *end == L'8',
|
|
|
|
L"converting invalid num to int did not fail");
|
|
|
|
|
|
|
|
l = fish_wcstol(L"");
|
|
|
|
do_test1(errno == EINVAL && l == 0, L"converting empty string to long did not fail");
|
|
|
|
l = fish_wcstol(L" \t ");
|
|
|
|
do_test1(errno == EINVAL && l == 0, L"converting whitespace string to long did not fail");
|
|
|
|
l = fish_wcstol(L"123");
|
|
|
|
do_test1(errno == 0 && l == 123, L"converting valid num to long did not succeed");
|
|
|
|
l = fish_wcstol(L"-123");
|
|
|
|
do_test1(errno == 0 && l == -123, L"converting valid num to long did not succeed");
|
|
|
|
l = fish_wcstol(L" 345 ");
|
|
|
|
do_test1(errno == 0 && l == 345, L"converting valid num to long did not succeed");
|
|
|
|
l = fish_wcstol(L" -345 ");
|
|
|
|
do_test1(errno == 0 && l == -345, L"converting valid num to long did not succeed");
|
|
|
|
l = fish_wcstol(L"x345");
|
|
|
|
do_test1(errno == EINVAL && l == 0, L"converting invalid num to long did not fail");
|
|
|
|
l = fish_wcstol(L" x345");
|
|
|
|
do_test1(errno == EINVAL && l == 0, L"converting invalid num to long did not fail");
|
|
|
|
l = fish_wcstol(L"456 x");
|
|
|
|
do_test1(errno == -1 && l == 456, L"converting invalid num to long did not fail");
|
|
|
|
l = fish_wcstol(L"99999999999999999999999");
|
|
|
|
do_test1(errno == ERANGE && l == LONG_MAX, L"converting invalid num to long did not fail");
|
|
|
|
l = fish_wcstol(L"-99999999999999999999999");
|
|
|
|
do_test1(errno == ERANGE && l == LONG_MIN, L"converting invalid num to long did not fail");
|
|
|
|
l = fish_wcstol(L"567]", &end);
|
|
|
|
do_test1(errno == -1 && l == 567 && *end == L']',
|
|
|
|
L"converting valid num to long did not succeed");
|
|
|
|
// This is subtle. "567" in base 8 is "375" in base 10. The final "8" is not converted.
|
|
|
|
l = fish_wcstol(L"5678", &end, 8);
|
|
|
|
do_test1(errno == -1 && l == 375 && *end == L'8',
|
|
|
|
L"converting invalid num to long did not fail");
|
|
|
|
}
|
|
|
|
|
2019-02-16 22:19:59 +00:00
|
|
|
enum class test_enum { alpha, beta, gamma, COUNT };
|
|
|
|
|
|
|
|
template <>
|
|
|
|
struct enum_info_t<test_enum> {
|
|
|
|
static constexpr auto count = test_enum::COUNT;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void test_enum_set() {
|
|
|
|
say(L"Testing enum set");
|
|
|
|
enum_set_t<test_enum> es;
|
|
|
|
do_test(es.none());
|
|
|
|
do_test(!es.any());
|
|
|
|
do_test(es.to_raw() == 0);
|
|
|
|
do_test(es == enum_set_t<test_enum>::from_raw(0));
|
|
|
|
do_test(es != enum_set_t<test_enum>::from_raw(1));
|
|
|
|
|
|
|
|
es.set(test_enum::beta);
|
2019-04-25 18:22:17 +00:00
|
|
|
do_test(es.get(test_enum::beta));
|
|
|
|
do_test(!es.get(test_enum::alpha));
|
|
|
|
do_test(es & test_enum::beta);
|
|
|
|
do_test(!(es & test_enum::alpha));
|
2019-02-16 22:19:59 +00:00
|
|
|
do_test(es.to_raw() == 2);
|
|
|
|
do_test(es == enum_set_t<test_enum>::from_raw(2));
|
|
|
|
do_test(es == enum_set_t<test_enum>{test_enum::beta});
|
|
|
|
do_test(es != enum_set_t<test_enum>::from_raw(3));
|
|
|
|
do_test(es.any());
|
|
|
|
do_test(!es.none());
|
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
do_test((enum_set_t<test_enum>{test_enum::beta} | test_enum::alpha).to_raw() == 3);
|
|
|
|
do_test((enum_set_t<test_enum>{test_enum::beta} | enum_set_t<test_enum>{test_enum::alpha})
|
|
|
|
.to_raw() == 3);
|
2019-02-16 22:19:59 +00:00
|
|
|
}
|
|
|
|
|
2019-02-12 04:23:15 +00:00
|
|
|
static void test_enum_array() {
|
|
|
|
say(L"Testing enum array");
|
|
|
|
enum_array_t<std::string, test_enum> es{};
|
|
|
|
do_test(es.size() == enum_count<test_enum>());
|
|
|
|
es[test_enum::beta] = "abc";
|
|
|
|
do_test(es[test_enum::beta] == "abc");
|
|
|
|
es.at(test_enum::gamma) = "def";
|
|
|
|
do_test(es.at(test_enum::gamma) == "def");
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Test sane escapes.
|
|
|
|
static void test_unescape_sane() {
|
|
|
|
const struct test_t {
|
|
|
|
const wchar_t *input;
|
|
|
|
const wchar_t *expected;
|
|
|
|
} tests[] = {
|
|
|
|
{L"abcd", L"abcd"}, {L"'abcd'", L"abcd"},
|
|
|
|
{L"'abcd\\n'", L"abcd\\n"}, {L"\"abcd\\n\"", L"abcd\\n"},
|
|
|
|
{L"\"abcd\\n\"", L"abcd\\n"}, {L"\\143", L"c"},
|
|
|
|
{L"'\\143'", L"\\143"}, {L"\\n", L"\n"} // \n normally becomes newline
|
2013-11-25 06:57:49 +00:00
|
|
|
};
|
2020-06-30 22:29:06 +00:00
|
|
|
for (const auto &test : tests) {
|
2023-03-26 15:23:05 +00:00
|
|
|
auto output = unescape_string(test.input, UNESCAPE_DEFAULT);
|
|
|
|
if (!output) {
|
2020-06-30 22:29:06 +00:00
|
|
|
err(L"Failed to unescape '%ls'\n", test.input);
|
2023-03-26 15:23:05 +00:00
|
|
|
} else if (*output != test.expected) {
|
2020-06-30 22:29:06 +00:00
|
|
|
err(L"In unescaping '%ls', expected '%ls' but got '%ls'\n", test.input, test.expected,
|
2023-03-26 15:23:05 +00:00
|
|
|
output->c_str());
|
2013-11-25 06:57:49 +00:00
|
|
|
}
|
|
|
|
}
|
2013-11-26 18:52:03 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Test for overflow.
|
2023-03-26 15:23:05 +00:00
|
|
|
if (unescape_string(L"echo \\UFFFFFF", UNESCAPE_DEFAULT)) {
|
2013-11-26 18:52:03 +00:00
|
|
|
err(L"Should not have been able to unescape \\UFFFFFF\n");
|
|
|
|
}
|
2023-03-26 15:23:05 +00:00
|
|
|
if (unescape_string(L"echo \\U110000", UNESCAPE_DEFAULT)) {
|
2013-11-26 18:52:03 +00:00
|
|
|
err(L"Should not have been able to unescape \\U110000\n");
|
|
|
|
}
|
2016-11-21 02:25:49 +00:00
|
|
|
#if WCHAR_MAX != 0xffff
|
|
|
|
// TODO: Make this work on MS Windows.
|
2023-03-26 15:23:05 +00:00
|
|
|
if (!unescape_string(L"echo \\U10FFFF", UNESCAPE_DEFAULT)) {
|
2016-11-21 02:25:49 +00:00
|
|
|
err(L"Should have been able to unescape \\U10FFFF\n");
|
2013-11-26 18:52:03 +00:00
|
|
|
}
|
2016-11-21 02:25:49 +00:00
|
|
|
#endif
|
2013-11-25 06:57:49 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Test the escaping/unescaping code by escaping/unescaping random strings and verifying that the
|
|
|
|
/// original string comes back.
|
|
|
|
static void test_escape_crazy() {
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Testing escaping and unescaping");
|
2013-11-25 06:57:49 +00:00
|
|
|
wcstring random_string;
|
|
|
|
wcstring escaped_string;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < ESCAPE_TEST_COUNT; i++) {
|
2013-11-25 06:57:49 +00:00
|
|
|
random_string.clear();
|
2019-02-18 13:44:41 +00:00
|
|
|
while (random() % ESCAPE_TEST_LENGTH) {
|
|
|
|
random_string.push_back((random() % ESCAPE_TEST_CHAR) + 1);
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2022-07-25 14:25:04 +00:00
|
|
|
escaped_string = escape_string(random_string);
|
2023-03-26 15:23:05 +00:00
|
|
|
auto unescaped_string = unescape_string(escaped_string, UNESCAPE_DEFAULT);
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2023-03-26 15:23:05 +00:00
|
|
|
if (!unescaped_string) {
|
2013-11-25 06:57:49 +00:00
|
|
|
err(L"Failed to unescape string <%ls>", escaped_string.c_str());
|
2020-09-26 08:43:05 +00:00
|
|
|
break;
|
2023-03-26 15:23:05 +00:00
|
|
|
} else if (*unescaped_string != random_string) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"Escaped and then unescaped string '%ls', but got back a different string '%ls'",
|
2023-03-26 15:23:05 +00:00
|
|
|
random_string.c_str(), unescaped_string->c_str());
|
2020-09-26 08:43:05 +00:00
|
|
|
break;
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-14 03:19:08 +00:00
|
|
|
|
2022-07-25 14:25:04 +00:00
|
|
|
// Verify that ESCAPE_NO_PRINTABLES also escapes backslashes so we don't regress on issue #3892.
|
2017-03-14 03:19:08 +00:00
|
|
|
random_string = L"line 1\\n\nline 2";
|
2022-07-25 14:25:04 +00:00
|
|
|
escaped_string = escape_string(random_string, ESCAPE_NO_PRINTABLES | ESCAPE_NO_QUOTED);
|
2023-03-26 15:23:05 +00:00
|
|
|
auto unescaped_string = unescape_string(escaped_string, UNESCAPE_DEFAULT);
|
|
|
|
if (!unescaped_string) {
|
2017-03-14 03:19:08 +00:00
|
|
|
err(L"Failed to unescape string <%ls>", escaped_string.c_str());
|
2023-03-26 15:23:05 +00:00
|
|
|
} else if (*unescaped_string != random_string) {
|
2017-03-14 03:19:08 +00:00
|
|
|
err(L"Escaped and then unescaped string '%ls', but got back a different string '%ls'",
|
2023-03-26 15:23:05 +00:00
|
|
|
random_string.c_str(), unescaped_string->c_str());
|
2017-03-14 03:19:08 +00:00
|
|
|
}
|
2012-02-29 19:27:14 +00:00
|
|
|
}
|
2007-09-23 20:59:18 +00:00
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_format() {
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Testing formatting functions");
|
2016-05-01 01:37:19 +00:00
|
|
|
struct {
|
2012-11-19 00:30:30 +00:00
|
|
|
unsigned long long val;
|
|
|
|
const char *expected;
|
2016-05-01 01:37:19 +00:00
|
|
|
} tests[] = {{0, "empty"}, {1, "1B"}, {2, "2B"},
|
|
|
|
{1024, "1kB"}, {1870, "1.8kB"}, {4322911, "4.1MB"}};
|
2020-06-30 22:29:06 +00:00
|
|
|
for (const auto &test : tests) {
|
2012-02-29 19:27:14 +00:00
|
|
|
char buff[128];
|
2020-06-30 22:29:06 +00:00
|
|
|
format_size_safe(buff, test.val);
|
|
|
|
do_test(!std::strcmp(buff, test.expected));
|
2012-02-29 19:27:14 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
for (int j = -129; j <= 129; j++) {
|
2012-02-29 19:27:14 +00:00
|
|
|
char buff1[128], buff2[128];
|
2012-03-03 23:20:30 +00:00
|
|
|
format_long_safe(buff1, j);
|
2022-06-25 23:20:33 +00:00
|
|
|
snprintf(buff2, 128, "%d", j);
|
2019-03-12 22:07:07 +00:00
|
|
|
do_test(!std::strcmp(buff1, buff2));
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2015-01-19 00:03:21 +00:00
|
|
|
wchar_t wbuf1[128], wbuf2[128];
|
|
|
|
format_long_safe(wbuf1, j);
|
2019-03-12 21:06:01 +00:00
|
|
|
std::swprintf(wbuf2, 128, L"%d", j);
|
|
|
|
do_test(!std::wcscmp(wbuf1, wbuf2));
|
2012-03-03 23:20:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2012-03-03 23:20:30 +00:00
|
|
|
long q = LONG_MIN;
|
|
|
|
char buff1[128], buff2[128];
|
|
|
|
format_long_safe(buff1, q);
|
2022-06-25 23:20:33 +00:00
|
|
|
snprintf(buff2, 128, "%ld", q);
|
2019-03-12 22:07:07 +00:00
|
|
|
do_test(!std::strcmp(buff1, buff2));
|
2007-09-23 20:59:18 +00:00
|
|
|
}
|
|
|
|
|
2016-05-19 00:46:13 +00:00
|
|
|
/// Helper to convert a narrow string to a sequence of hex digits.
|
2022-04-09 22:19:18 +00:00
|
|
|
static std::string str2hex(const std::string &input) {
|
|
|
|
std::string output;
|
|
|
|
for (char c : input) {
|
|
|
|
char buff[16];
|
|
|
|
size_t amt = snprintf(buff, sizeof buff, "0x%02X ", (int)(c & 0xFF));
|
|
|
|
output.append(buff, amt);
|
2016-05-19 00:46:13 +00:00
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Test wide/narrow conversion by creating random strings and verifying that the original string
|
2016-05-23 02:00:13 +00:00
|
|
|
/// comes back through double conversion.
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_convert() {
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Testing wide/narrow string conversion");
|
2020-07-29 23:37:23 +00:00
|
|
|
for (int i = 0; i < ESCAPE_TEST_COUNT; i++) {
|
|
|
|
std::string orig{};
|
2019-02-18 13:44:41 +00:00
|
|
|
while (random() % ESCAPE_TEST_LENGTH) {
|
2020-07-29 23:37:23 +00:00
|
|
|
char c = random();
|
|
|
|
orig.push_back(c);
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2020-07-29 23:37:23 +00:00
|
|
|
const wcstring w = str2wcstring(orig);
|
|
|
|
std::string n = wcs2string(w);
|
|
|
|
if (orig != n) {
|
2016-05-19 00:46:13 +00:00
|
|
|
err(L"Line %d - %d: Conversion cycle of string:\n%4d chars: %s\n"
|
|
|
|
L"produced different string:\n%4d chars: %s",
|
2022-04-09 22:19:18 +00:00
|
|
|
__LINE__, i, orig.size(), str2hex(orig).c_str(), n.size(), str2hex(n).c_str());
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
|
|
|
}
|
2012-12-19 21:31:06 +00:00
|
|
|
}
|
|
|
|
|
2020-09-19 21:04:47 +00:00
|
|
|
/// Verify that ASCII narrow->wide conversions are correct.
|
|
|
|
static void test_convert_ascii() {
|
|
|
|
std::string s(4096, '\0');
|
|
|
|
for (size_t i = 0; i < s.size(); i++) {
|
|
|
|
s[i] = (i % 10) + '0';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test a variety of alignments.
|
|
|
|
for (size_t left = 0; left < 16; left++) {
|
|
|
|
for (size_t right = 0; right < 16; right++) {
|
|
|
|
const char *start = s.data() + left;
|
|
|
|
size_t len = s.size() - left - right;
|
|
|
|
wcstring wide = str2wcstring(start, len);
|
|
|
|
std::string narrow = wcs2string(wide);
|
|
|
|
do_test(narrow == std::string(start, len));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put some non-ASCII bytes in and ensure it all still works.
|
|
|
|
for (char &c : s) {
|
|
|
|
char saved = c;
|
|
|
|
c = 0xF7;
|
|
|
|
do_test(wcs2string(str2wcstring(s)) == s);
|
|
|
|
c = saved;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-21 20:31:58 +00:00
|
|
|
/// fish uses the private-use range to encode bytes that could not be decoded using the user's
|
|
|
|
/// locale. If the input could be decoded, but decoded to private-use codepoints, then fish should
|
|
|
|
/// also use the direct encoding for those bytes. Verify that characters in the private use area are
|
|
|
|
/// correctly round-tripped. See #7723.
|
2021-02-17 20:29:51 +00:00
|
|
|
static void test_convert_private_use() {
|
|
|
|
for (wchar_t wc = ENCODE_DIRECT_BASE; wc < ENCODE_DIRECT_END; wc++) {
|
2021-04-21 20:31:58 +00:00
|
|
|
// Encode the char via the locale. Do not use fish functions which interpret these
|
|
|
|
// specially.
|
2021-02-17 20:29:51 +00:00
|
|
|
char converted[MB_LEN_MAX];
|
|
|
|
mbstate_t state{};
|
|
|
|
size_t len = std::wcrtomb(converted, wc, &state);
|
|
|
|
if (len == static_cast<size_t>(-1)) {
|
|
|
|
// Could not be encoded in this locale.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
std::string s(converted, len);
|
|
|
|
|
|
|
|
// Ask fish to decode this via str2wcstring.
|
2021-04-21 20:31:58 +00:00
|
|
|
// str2wcstring should notice that the decoded form collides with its private use and encode
|
|
|
|
// it directly.
|
2021-02-17 20:29:51 +00:00
|
|
|
wcstring ws = str2wcstring(s);
|
|
|
|
|
|
|
|
// Each byte should be encoded directly, and round tripping should work.
|
|
|
|
do_test(ws.size() == s.size());
|
|
|
|
do_test(wcs2string(ws) == s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-19 21:04:47 +00:00
|
|
|
static void perf_convert_ascii() {
|
|
|
|
std::string s(128 * 1024, '\0');
|
|
|
|
for (size_t i = 0; i < s.size(); i++) {
|
|
|
|
s[i] = (i % 10) + '0';
|
|
|
|
}
|
|
|
|
(void)str2wcstring(s);
|
|
|
|
|
|
|
|
double start = timef();
|
|
|
|
const int iters = 1024;
|
2020-09-27 15:53:18 +00:00
|
|
|
for (int i = 0; i < iters; i++) {
|
2020-09-19 21:04:47 +00:00
|
|
|
(void)str2wcstring(s);
|
|
|
|
}
|
|
|
|
double end = timef();
|
|
|
|
auto usec = static_cast<unsigned long long>(((end - start) * 1E6) / iters);
|
|
|
|
say(L"ASCII string conversion perf: %lu bytes in %llu usec", s.size(), usec);
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Verify correct behavior with embedded nulls.
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_convert_nulls() {
|
2016-05-23 02:00:13 +00:00
|
|
|
say(L"Testing convert_nulls");
|
2012-12-19 21:31:06 +00:00
|
|
|
const wchar_t in[] = L"AAA\0BBB";
|
|
|
|
const size_t in_len = (sizeof in / sizeof *in) - 1;
|
|
|
|
const wcstring in_str = wcstring(in, in_len);
|
|
|
|
std::string out_str = wcs2string(in_str);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (out_str.size() != in_len) {
|
2012-12-19 21:31:06 +00:00
|
|
|
err(L"Embedded nulls mishandled in wcs2string");
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < in_len; i++) {
|
|
|
|
if (in[i] != out_str.at(i)) {
|
2012-12-19 21:31:06 +00:00
|
|
|
err(L"Embedded nulls mishandled in wcs2string at index %lu", (unsigned long)i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wcstring out_wstr = str2wcstring(out_str);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (out_wstr.size() != in_len) {
|
2012-12-19 21:31:06 +00:00
|
|
|
err(L"Embedded nulls mishandled in str2wcstring");
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < in_len; i++) {
|
|
|
|
if (in[i] != out_wstr.at(i)) {
|
2012-12-19 21:31:06 +00:00
|
|
|
err(L"Embedded nulls mishandled in str2wcstring at index %lu", (unsigned long)i);
|
|
|
|
}
|
|
|
|
}
|
2007-09-23 20:59:18 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Test the tokenizer.
|
2016-05-23 02:00:13 +00:00
|
|
|
static void test_tokenizer() {
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Testing tokenizer");
|
2018-02-19 23:10:10 +00:00
|
|
|
{
|
|
|
|
const wchar_t *str = L"alpha beta";
|
2023-02-05 13:06:11 +00:00
|
|
|
auto t = new_tokenizer(str, 0);
|
|
|
|
std::unique_ptr<tok_t> token{};
|
2019-10-13 23:06:16 +00:00
|
|
|
|
2023-02-05 13:06:11 +00:00
|
|
|
token = t->next(); // alpha
|
|
|
|
do_test(token);
|
|
|
|
do_test(token->type_ == token_type_t::string);
|
2019-10-13 23:06:16 +00:00
|
|
|
do_test(token->offset == 0);
|
|
|
|
do_test(token->length == 5);
|
2023-02-05 13:06:11 +00:00
|
|
|
do_test(*t->text_of(*token) == L"alpha");
|
2019-10-13 23:06:16 +00:00
|
|
|
|
2023-02-05 13:06:11 +00:00
|
|
|
token = t->next(); // beta
|
|
|
|
do_test(token);
|
|
|
|
do_test(token->type_ == token_type_t::string);
|
2019-10-13 23:06:16 +00:00
|
|
|
do_test(token->offset == 6);
|
|
|
|
do_test(token->length == 4);
|
2023-02-05 13:06:11 +00:00
|
|
|
do_test(*t->text_of(*token) == L"beta");
|
2019-10-13 23:06:16 +00:00
|
|
|
|
2023-02-05 13:06:11 +00:00
|
|
|
token = t->next();
|
|
|
|
do_test(!token);
|
2018-02-19 23:10:10 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
const wchar_t *str =
|
|
|
|
L"string <redirection 2>&1 'nested \"quoted\" '(string containing subshells "
|
2018-04-24 22:53:30 +00:00
|
|
|
L"){and,brackets}$as[$well (as variable arrays)] not_a_redirect^ ^ ^^is_a_redirect "
|
2019-10-14 22:45:40 +00:00
|
|
|
L"&| &> "
|
2018-03-01 20:56:15 +00:00
|
|
|
L"&&& ||| "
|
|
|
|
L"&& || & |"
|
2016-05-01 01:37:19 +00:00
|
|
|
L"Compress_Newlines\n \n\t\n \nInto_Just_One";
|
2019-10-13 23:06:16 +00:00
|
|
|
using tt = token_type_t;
|
|
|
|
const token_type_t types[] = {
|
2021-04-21 20:31:58 +00:00
|
|
|
tt::string, tt::redirect, tt::string, tt::redirect, tt::string, tt::string,
|
|
|
|
tt::string, tt::string, tt::string, tt::pipe, tt::redirect, tt::andand,
|
|
|
|
tt::background, tt::oror, tt::pipe, tt::andand, tt::oror, tt::background,
|
|
|
|
tt::pipe, tt::string, tt::end, tt::string};
|
2012-11-19 00:30:30 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
say(L"Test correct tokenization");
|
2012-11-23 19:12:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
{
|
2023-02-05 13:06:11 +00:00
|
|
|
auto t = new_tokenizer(str, 0);
|
2015-07-26 06:05:47 +00:00
|
|
|
size_t i = 0;
|
2023-02-05 13:06:11 +00:00
|
|
|
while (auto token = t->next()) {
|
2018-02-19 23:10:10 +00:00
|
|
|
if (i >= sizeof types / sizeof *types) {
|
2015-07-26 06:05:47 +00:00
|
|
|
err(L"Too many tokens returned from tokenizer");
|
2023-02-05 13:06:11 +00:00
|
|
|
std::fwprintf(stdout, L"Got excess token type %ld\n", (long)token->type_);
|
2015-07-26 06:05:47 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-02-05 13:06:11 +00:00
|
|
|
if (types[i] != token->type_) {
|
2012-11-19 00:30:30 +00:00
|
|
|
err(L"Tokenization error:");
|
2019-05-05 10:09:25 +00:00
|
|
|
std::fwprintf(
|
|
|
|
stdout,
|
|
|
|
L"Token number %zu of string \n'%ls'\n, expected type %ld, got token type "
|
|
|
|
L"%ld\n",
|
2023-02-05 13:06:11 +00:00
|
|
|
i + 1, str, (long)types[i], (long)token->type_);
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2015-07-26 06:05:47 +00:00
|
|
|
i++;
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
if (i < sizeof types / sizeof *types) {
|
2015-07-26 06:05:47 +00:00
|
|
|
err(L"Too few tokens returned from tokenizer");
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Test some errors.
|
2015-08-11 01:30:44 +00:00
|
|
|
{
|
2023-02-05 13:06:11 +00:00
|
|
|
auto t = new_tokenizer(L"abc\\", 0);
|
|
|
|
auto token = t->next();
|
|
|
|
do_test(token);
|
|
|
|
do_test(token->type_ == token_type_t::error);
|
2019-10-13 23:06:16 +00:00
|
|
|
do_test(token->error == tokenizer_error_t::unterminated_escape);
|
2019-11-09 00:55:54 +00:00
|
|
|
do_test(token->error_offset_within_token == 3);
|
2015-08-11 01:30:44 +00:00
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2018-03-11 22:16:53 +00:00
|
|
|
{
|
2023-02-05 13:06:11 +00:00
|
|
|
auto t = new_tokenizer(L"abc )defg(hij", 0);
|
|
|
|
auto token = t->next();
|
|
|
|
do_test(token);
|
|
|
|
token = t->next();
|
|
|
|
do_test(token);
|
|
|
|
do_test(token->type_ == token_type_t::error);
|
2019-10-13 23:06:16 +00:00
|
|
|
do_test(token->error == tokenizer_error_t::closing_unopened_subshell);
|
2019-11-09 00:53:13 +00:00
|
|
|
do_test(token->offset == 4);
|
2019-11-09 00:55:54 +00:00
|
|
|
do_test(token->error_offset_within_token == 0);
|
2018-03-11 22:16:53 +00:00
|
|
|
}
|
|
|
|
|
2015-08-11 01:30:44 +00:00
|
|
|
{
|
2023-02-05 13:06:11 +00:00
|
|
|
auto t = new_tokenizer(L"abc defg(hij (klm)", 0);
|
|
|
|
auto token = t->next();
|
|
|
|
do_test(token);
|
|
|
|
token = t->next();
|
|
|
|
do_test(token);
|
|
|
|
do_test(token->type_ == token_type_t::error);
|
2019-10-13 23:06:16 +00:00
|
|
|
do_test(token->error == tokenizer_error_t::unterminated_subshell);
|
2019-11-09 00:55:54 +00:00
|
|
|
do_test(token->error_offset_within_token == 4);
|
2015-08-11 01:30:44 +00:00
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2015-08-11 02:30:21 +00:00
|
|
|
{
|
2023-02-05 13:06:11 +00:00
|
|
|
auto t = new_tokenizer(L"abc defg[hij (klm)", 0);
|
|
|
|
auto token = t->next();
|
|
|
|
do_test(token);
|
|
|
|
token = t->next();
|
|
|
|
do_test(token);
|
|
|
|
do_test(token->type_ == token_type_t::error);
|
2019-10-13 23:06:16 +00:00
|
|
|
do_test(token->error == tokenizer_error_t::unterminated_slice);
|
2019-11-09 00:55:54 +00:00
|
|
|
do_test(token->error_offset_within_token == 4);
|
2015-08-11 02:30:21 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-10-14 20:20:31 +00:00
|
|
|
// Test some redirection parsing.
|
2023-02-05 13:06:11 +00:00
|
|
|
auto pipe_or_redir = [](const wchar_t *s) { return pipe_or_redir_from_string(s); };
|
2019-10-14 20:20:31 +00:00
|
|
|
do_test(pipe_or_redir(L"|")->is_pipe);
|
|
|
|
do_test(pipe_or_redir(L"0>|")->is_pipe);
|
|
|
|
do_test(pipe_or_redir(L"0>|")->fd == 0);
|
|
|
|
do_test(pipe_or_redir(L"2>|")->is_pipe);
|
|
|
|
do_test(pipe_or_redir(L"2>|")->fd == 2);
|
|
|
|
do_test(pipe_or_redir(L">|")->is_pipe);
|
|
|
|
do_test(pipe_or_redir(L">|")->fd == STDOUT_FILENO);
|
|
|
|
do_test(!pipe_or_redir(L">")->is_pipe);
|
|
|
|
do_test(pipe_or_redir(L">")->fd == STDOUT_FILENO);
|
|
|
|
do_test(pipe_or_redir(L"2>")->fd == STDERR_FILENO);
|
|
|
|
do_test(pipe_or_redir(L"9999999999999>")->fd == -1);
|
|
|
|
do_test(pipe_or_redir(L"9999999999999>&2")->fd == -1);
|
|
|
|
do_test(pipe_or_redir(L"9999999999999>&2")->is_valid() == false);
|
|
|
|
do_test(pipe_or_redir(L"9999999999999>&2")->is_valid() == false);
|
|
|
|
|
2019-10-14 22:45:40 +00:00
|
|
|
do_test(pipe_or_redir(L"&|")->is_pipe);
|
|
|
|
do_test(pipe_or_redir(L"&|")->stderr_merge);
|
|
|
|
do_test(!pipe_or_redir(L"&>")->is_pipe);
|
|
|
|
do_test(pipe_or_redir(L"&>")->stderr_merge);
|
|
|
|
do_test(pipe_or_redir(L"&>>")->stderr_merge);
|
|
|
|
do_test(pipe_or_redir(L"&>?")->stderr_merge);
|
|
|
|
|
2019-10-14 20:20:31 +00:00
|
|
|
auto get_redir_mode = [](const wchar_t *s) -> maybe_t<redirection_mode_t> {
|
2023-02-05 13:06:11 +00:00
|
|
|
if (auto redir = pipe_or_redir_from_string(s)) {
|
2019-10-14 20:20:31 +00:00
|
|
|
return redir->mode;
|
|
|
|
}
|
|
|
|
return none();
|
|
|
|
};
|
|
|
|
|
|
|
|
if (get_redir_mode(L"<") != redirection_mode_t::input)
|
|
|
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
|
|
|
if (get_redir_mode(L">") != redirection_mode_t::overwrite)
|
2018-04-24 22:53:30 +00:00
|
|
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
2019-10-14 20:20:31 +00:00
|
|
|
if (get_redir_mode(L"2>") != redirection_mode_t::overwrite)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
2019-10-14 20:20:31 +00:00
|
|
|
if (get_redir_mode(L">>") != redirection_mode_t::append)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
2019-10-14 20:20:31 +00:00
|
|
|
if (get_redir_mode(L"2>>") != redirection_mode_t::append)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
2019-10-14 20:20:31 +00:00
|
|
|
if (get_redir_mode(L"2>?") != redirection_mode_t::noclob)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
2019-10-14 20:20:31 +00:00
|
|
|
if (get_redir_mode(L"9999999999999999>?") != redirection_mode_t::noclob)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
2019-10-14 20:20:31 +00:00
|
|
|
if (get_redir_mode(L"2>&3") != redirection_mode_t::fd)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
2019-10-14 20:20:31 +00:00
|
|
|
if (get_redir_mode(L"3<&0") != redirection_mode_t::fd)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
2019-10-14 20:20:31 +00:00
|
|
|
if (get_redir_mode(L"3</tmp/filetxt") != redirection_mode_t::input)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_iothread() {
|
2013-11-28 00:04:12 +00:00
|
|
|
say(L"Testing iothreads");
|
2022-06-19 21:19:17 +00:00
|
|
|
std::atomic<int> shared_int{0};
|
|
|
|
const int iterations = 64;
|
|
|
|
std::promise<void> prom;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (int i = 0; i < iterations; i++) {
|
2022-06-19 21:19:17 +00:00
|
|
|
iothread_perform([&] {
|
|
|
|
int newv = 1 + shared_int.fetch_add(1, std::memory_order_relaxed);
|
|
|
|
if (newv == iterations) {
|
|
|
|
prom.set_value();
|
|
|
|
}
|
|
|
|
});
|
2013-11-28 00:04:12 +00:00
|
|
|
}
|
2022-06-19 21:19:17 +00:00
|
|
|
auto status = prom.get_future().wait_for(std::chrono::seconds(64));
|
2013-11-28 00:04:12 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Should have incremented it once per thread.
|
2022-06-19 21:19:17 +00:00
|
|
|
do_test(status == std::future_status::ready);
|
|
|
|
do_test(shared_int == iterations);
|
|
|
|
if (shared_int != iterations) {
|
|
|
|
say(L"Expected int to be %d, but instead it was %d", iterations, shared_int.load());
|
2013-11-28 00:04:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-01 09:04:14 +00:00
|
|
|
static void test_pthread() {
|
|
|
|
say(L"Testing pthreads");
|
2019-12-16 22:08:28 +00:00
|
|
|
std::atomic<int> val{3};
|
|
|
|
std::promise<void> promise;
|
|
|
|
bool made = make_detached_pthread([&]() {
|
|
|
|
val = val + 2;
|
|
|
|
promise.set_value();
|
|
|
|
});
|
2019-02-01 09:04:14 +00:00
|
|
|
do_test(made);
|
2019-12-16 22:08:28 +00:00
|
|
|
promise.get_future().wait();
|
2019-02-01 09:04:14 +00:00
|
|
|
do_test(val == 5);
|
|
|
|
}
|
|
|
|
|
2020-03-03 06:57:41 +00:00
|
|
|
static void test_debounce() {
|
|
|
|
say(L"Testing debounce");
|
|
|
|
// Run 8 functions using a condition variable.
|
|
|
|
// Only the first and last should run.
|
2023-04-26 02:38:53 +00:00
|
|
|
auto db = new_debounce_t(0);
|
2020-03-03 06:57:41 +00:00
|
|
|
constexpr size_t count = 8;
|
|
|
|
std::array<bool, count> handler_ran = {};
|
|
|
|
std::array<bool, count> completion_ran = {};
|
|
|
|
|
|
|
|
bool ready_to_go = false;
|
|
|
|
std::mutex m;
|
|
|
|
std::condition_variable cv;
|
|
|
|
|
|
|
|
// "Enqueue" all functions. Each one waits until ready_to_go.
|
|
|
|
for (size_t idx = 0; idx < count; idx++) {
|
|
|
|
do_test(handler_ran[idx] == false);
|
2023-04-26 02:38:53 +00:00
|
|
|
std::function<size_t()> performer = [&, idx] {
|
|
|
|
std::unique_lock<std::mutex> lock(m);
|
|
|
|
cv.wait(lock, [&] { return ready_to_go; });
|
|
|
|
handler_ran[idx] = true;
|
|
|
|
return idx;
|
|
|
|
};
|
|
|
|
std::function<void(size_t)> completer = [&](size_t idx) { completion_ran[idx] = true; };
|
|
|
|
debounce_perform_with_completion(*db, std::move(performer), std::move(completer));
|
2020-03-03 06:57:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// We're ready to go.
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock(m);
|
|
|
|
ready_to_go = true;
|
|
|
|
}
|
|
|
|
cv.notify_all();
|
|
|
|
|
|
|
|
// Wait until the last completion is done.
|
|
|
|
while (!completion_ran.back()) {
|
2021-02-07 02:43:43 +00:00
|
|
|
iothread_service_main();
|
2020-03-03 06:57:41 +00:00
|
|
|
}
|
|
|
|
iothread_drain_all();
|
|
|
|
|
|
|
|
// Each perform() call may displace an existing queued operation.
|
|
|
|
// Each operation waits until all are queued.
|
|
|
|
// Therefore we expect the last perform() to have run, and at most one more.
|
|
|
|
|
|
|
|
do_test(handler_ran.back());
|
|
|
|
do_test(completion_ran.back());
|
|
|
|
|
|
|
|
size_t total_ran = 0;
|
|
|
|
for (size_t idx = 0; idx < count; idx++) {
|
|
|
|
total_ran += (handler_ran[idx] ? 1 : 0);
|
|
|
|
do_test(handler_ran[idx] == completion_ran[idx]);
|
|
|
|
}
|
|
|
|
do_test(total_ran <= 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_debounce_timeout() {
|
|
|
|
using namespace std::chrono;
|
|
|
|
say(L"Testing debounce timeout");
|
|
|
|
|
|
|
|
// Verify that debounce doesn't wait forever.
|
|
|
|
// Use a shared_ptr so we don't have to join our threads.
|
2022-11-12 21:26:21 +00:00
|
|
|
const long timeout_ms = 500;
|
2020-03-03 06:57:41 +00:00
|
|
|
struct data_t {
|
2023-04-26 02:38:53 +00:00
|
|
|
rust::box<debounce_t> db = new_debounce_t(timeout_ms);
|
2020-03-03 06:57:41 +00:00
|
|
|
bool exit_ok = false;
|
|
|
|
std::mutex m;
|
|
|
|
std::condition_variable cv;
|
|
|
|
relaxed_atomic_t<uint32_t> running{0};
|
|
|
|
};
|
|
|
|
auto data = std::make_shared<data_t>();
|
|
|
|
|
|
|
|
// Our background handler. Note this just blocks until exit_ok is set.
|
|
|
|
std::function<void()> handler = [data] {
|
|
|
|
data->running++;
|
|
|
|
std::unique_lock<std::mutex> lock(data->m);
|
|
|
|
data->cv.wait(lock, [&] { return data->exit_ok; });
|
|
|
|
};
|
|
|
|
|
|
|
|
// Spawn the handler twice. This should not modify the thread token.
|
2023-04-26 02:38:53 +00:00
|
|
|
uint64_t token1 = debounce_perform(*data->db, handler);
|
|
|
|
uint64_t token2 = debounce_perform(*data->db, handler);
|
2020-03-03 06:57:41 +00:00
|
|
|
do_test(token1 == token2);
|
|
|
|
|
|
|
|
// Wait 75 msec, then enqueue something else; this should spawn a new thread.
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(timeout_ms + timeout_ms / 2));
|
|
|
|
do_test(data->running == 1);
|
2023-04-26 02:38:53 +00:00
|
|
|
uint64_t token3 = debounce_perform(*data->db, handler);
|
2020-03-03 06:57:41 +00:00
|
|
|
do_test(token3 > token2);
|
|
|
|
|
|
|
|
// Release all the threads.
|
|
|
|
std::unique_lock<std::mutex> lock(data->m);
|
|
|
|
data->exit_ok = true;
|
|
|
|
data->cv.notify_all();
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static parser_test_error_bits_t detect_argument_errors(const wcstring &src) {
|
2020-07-02 04:06:58 +00:00
|
|
|
using namespace ast;
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
auto ast = ast_parse_argument_list(src, parse_flag_none);
|
|
|
|
if (ast->errored()) {
|
2014-03-18 15:51:23 +00:00
|
|
|
return PARSER_TEST_ERROR;
|
|
|
|
}
|
2020-07-02 04:06:58 +00:00
|
|
|
const ast::argument_t *first_arg =
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
ast->top()->as_freestanding_argument_list().arguments().at(0);
|
2020-07-02 04:06:58 +00:00
|
|
|
if (!first_arg) {
|
|
|
|
err(L"Failed to parse an argument");
|
|
|
|
return 0;
|
|
|
|
}
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
return parse_util_detect_errors_in_argument(*first_arg, *first_arg->source(src));
|
2014-03-18 15:51:23 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Test the parser.
|
|
|
|
static void test_parser() {
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Testing parser");
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
auto detect_errors = [](const wcstring &s) {
|
|
|
|
return parse_util_detect_errors(s, nullptr, true /* accept incomplete */);
|
|
|
|
};
|
2019-05-05 02:16:26 +00:00
|
|
|
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Testing block nesting");
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"if; end")) {
|
2012-11-19 00:30:30 +00:00
|
|
|
err(L"Incomplete if statement undetected");
|
|
|
|
}
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"if test; echo")) {
|
2012-11-19 00:30:30 +00:00
|
|
|
err(L"Missing end undetected");
|
|
|
|
}
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"if test; end; end")) {
|
2012-11-19 00:30:30 +00:00
|
|
|
err(L"Unbalanced end undetected");
|
|
|
|
}
|
|
|
|
|
|
|
|
say(L"Testing detection of invalid use of builtin commands");
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"case foo")) {
|
2012-11-19 00:30:30 +00:00
|
|
|
err(L"'case' command outside of block context undetected");
|
|
|
|
}
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"switch ggg; if true; case foo;end;end")) {
|
2012-11-19 00:30:30 +00:00
|
|
|
err(L"'case' command outside of switch block context undetected");
|
|
|
|
}
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"else")) {
|
2012-11-19 00:30:30 +00:00
|
|
|
err(L"'else' command outside of conditional block context undetected");
|
|
|
|
}
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"else if")) {
|
2012-11-19 00:30:30 +00:00
|
|
|
err(L"'else if' command outside of conditional block context undetected");
|
|
|
|
}
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"if false; else if; end")) {
|
2012-11-19 00:30:30 +00:00
|
|
|
err(L"'else if' missing command undetected");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"break")) {
|
2012-11-19 00:30:30 +00:00
|
|
|
err(L"'break' command outside of loop block context undetected");
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L"break --help")) {
|
2013-12-13 02:18:07 +00:00
|
|
|
err(L"'break --help' incorrectly marked as error");
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"while false ; function foo ; break ; end ; end ")) {
|
2013-12-13 02:18:07 +00:00
|
|
|
err(L"'break' command inside function allowed to break from loop outside it");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"exec ls|less") || !detect_errors(L"echo|return")) {
|
2012-11-19 00:30:30 +00:00
|
|
|
err(L"Invalid pipe command undetected");
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L"for i in foo ; switch $i ; case blah ; break; end; end ")) {
|
2013-12-13 02:18:07 +00:00
|
|
|
err(L"'break' command inside switch falsely reported as error");
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L"or cat | cat") || detect_errors(L"and cat | cat")) {
|
2014-01-13 21:14:18 +00:00
|
|
|
err(L"boolean command at beginning of pipeline falsely reported as error");
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"cat | and cat")) {
|
2014-01-13 21:14:18 +00:00
|
|
|
err(L"'and' command in pipeline not reported as error");
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"cat | or cat")) {
|
2014-03-18 15:51:23 +00:00
|
|
|
err(L"'or' command in pipeline not reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"cat | exec") || !detect_errors(L"exec | cat")) {
|
2014-01-13 21:14:18 +00:00
|
|
|
err(L"'exec' command in pipeline not reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"begin ; end arg")) {
|
2018-01-22 21:31:39 +00:00
|
|
|
err(L"argument to 'end' not reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"switch foo ; end arg")) {
|
2018-01-22 21:31:39 +00:00
|
|
|
err(L"argument to 'end' not reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"if true; else if false ; end arg")) {
|
2018-01-22 21:31:39 +00:00
|
|
|
err(L"argument to 'end' not reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"if true; else ; end arg")) {
|
2018-01-22 21:31:39 +00:00
|
|
|
err(L"argument to 'end' not reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L"begin ; end 2> /dev/null")) {
|
2018-01-22 21:31:39 +00:00
|
|
|
err(L"redirection after 'end' wrongly reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L"true | ") != PARSER_TEST_INCOMPLETE) {
|
2018-02-18 21:13:58 +00:00
|
|
|
err(L"unterminated pipe not reported properly");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L"echo (\nfoo\n bar") != PARSER_TEST_INCOMPLETE) {
|
2020-07-02 04:06:58 +00:00
|
|
|
err(L"unterminated multiline subshell not reported properly");
|
2019-11-14 02:01:47 +00:00
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L"begin ; true ; end | ") != PARSER_TEST_INCOMPLETE) {
|
2018-02-18 21:13:58 +00:00
|
|
|
err(L"unterminated pipe not reported properly");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L" | true ") != PARSER_TEST_ERROR) {
|
2018-02-18 21:13:58 +00:00
|
|
|
err(L"leading pipe not reported properly");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L"true | # comment") != PARSER_TEST_INCOMPLETE) {
|
2018-02-18 22:37:44 +00:00
|
|
|
err(L"comment after pipe not reported as incomplete");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L"true | # comment \n false ")) {
|
2018-02-18 22:37:44 +00:00
|
|
|
err(L"comment and newline after pipe wrongly reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L"true | ; false ") != PARSER_TEST_ERROR) {
|
2018-02-18 22:37:44 +00:00
|
|
|
err(L"semicolon after pipe not detected as error");
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
if (detect_argument_errors(L"foo")) {
|
2014-03-18 15:51:23 +00:00
|
|
|
err(L"simple argument reported as error");
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
if (detect_argument_errors(L"''")) {
|
2014-03-18 15:51:23 +00:00
|
|
|
err(L"Empty string reported as error");
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!(detect_argument_errors(L"foo$$") & PARSER_TEST_ERROR)) {
|
2014-03-18 15:51:23 +00:00
|
|
|
err(L"Bad variable expansion not reported as error");
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!(detect_argument_errors(L"foo$@") & PARSER_TEST_ERROR)) {
|
2014-03-18 15:51:23 +00:00
|
|
|
err(L"Bad variable expansion not reported as error");
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Within command substitutions, we should be able to detect everything that
|
|
|
|
// parse_util_detect_errors can detect.
|
|
|
|
if (!(detect_argument_errors(L"foo(cat | or cat)") & PARSER_TEST_ERROR)) {
|
2014-03-18 15:51:23 +00:00
|
|
|
err(L"Bad command substitution not reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"false & ; and cat")) {
|
2014-11-02 21:11:27 +00:00
|
|
|
err(L"'and' command after background not reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"true & ; or cat")) {
|
2014-11-02 21:11:27 +00:00
|
|
|
err(L"'or' command after background not reported as error");
|
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (detect_errors(L"true & ; not cat")) {
|
2014-11-02 21:11:27 +00:00
|
|
|
err(L"'not' command after background falsely reported as error");
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"if true & ; end")) {
|
2014-11-02 21:11:27 +00:00
|
|
|
err(L"backgrounded 'if' conditional not reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"if false; else if true & ; end")) {
|
2014-11-02 21:11:27 +00:00
|
|
|
err(L"backgrounded 'else if' conditional not reported as error");
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"while true & ; end")) {
|
2014-11-02 21:11:27 +00:00
|
|
|
err(L"backgrounded 'while' conditional not reported as error");
|
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"true | || false")) {
|
2018-03-01 21:39:39 +00:00
|
|
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"|| false")) {
|
2018-03-01 21:39:39 +00:00
|
|
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"&& false")) {
|
2018-03-01 21:39:39 +00:00
|
|
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"true ; && false")) {
|
2018-03-01 21:39:39 +00:00
|
|
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"true ; || false")) {
|
2018-03-01 21:39:39 +00:00
|
|
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"true || && false")) {
|
2018-03-01 21:39:39 +00:00
|
|
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"true && || false")) {
|
2018-03-01 21:39:39 +00:00
|
|
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:55:51 +00:00
|
|
|
if (!detect_errors(L"true && && false")) {
|
2018-03-01 21:39:39 +00:00
|
|
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
|
|
|
}
|
|
|
|
|
2020-08-04 19:39:37 +00:00
|
|
|
if (detect_errors(L"true && ") != PARSER_TEST_INCOMPLETE) {
|
|
|
|
err(L"unterminated conjunction not reported properly");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (detect_errors(L"true && \n true")) {
|
|
|
|
err(L"newline after && reported as error");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (detect_errors(L"true || \n") != PARSER_TEST_INCOMPLETE) {
|
|
|
|
err(L"unterminated conjunction not reported properly");
|
|
|
|
}
|
|
|
|
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Testing basic evaluation");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Ensure that we don't crash on infinite self recursion and mutual recursion. These must use
|
|
|
|
// the principal parser because we cannot yet execute jobs on other parsers.
|
2020-07-12 20:55:51 +00:00
|
|
|
auto parser = parser_t::principal_parser().shared();
|
2014-01-01 23:29:56 +00:00
|
|
|
say(L"Testing recursion detection");
|
2019-12-23 00:27:03 +00:00
|
|
|
parser->eval(L"function recursive ; recursive ; end ; recursive; ", io_chain_t());
|
2020-08-09 22:03:42 +00:00
|
|
|
|
|
|
|
parser->eval(
|
|
|
|
L"function recursive1 ; recursive2 ; end ; "
|
|
|
|
L"function recursive2 ; recursive1 ; end ; recursive1; ",
|
|
|
|
io_chain_t());
|
2014-03-17 15:45:25 +00:00
|
|
|
|
2014-05-06 10:31:44 +00:00
|
|
|
say(L"Testing empty function name");
|
2019-12-23 00:27:03 +00:00
|
|
|
parser->eval(L"function '' ; echo fail; exit 42 ; end ; ''", io_chain_t());
|
2014-05-06 10:31:44 +00:00
|
|
|
|
2014-03-17 15:45:25 +00:00
|
|
|
say(L"Testing eval_args");
|
2020-01-16 01:14:47 +00:00
|
|
|
completion_list_t comps = parser_t::expand_argument_list(L"alpha 'beta gamma' delta",
|
|
|
|
expand_flags_t{}, parser->context());
|
2014-03-17 15:45:25 +00:00
|
|
|
do_test(comps.size() == 3);
|
|
|
|
do_test(comps.at(0).completion == L"alpha");
|
|
|
|
do_test(comps.at(1).completion == L"beta gamma");
|
|
|
|
do_test(comps.at(2).completion == L"delta");
|
2012-01-27 19:43:45 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_1_cancellation(const wchar_t *src) {
|
2021-02-06 02:14:50 +00:00
|
|
|
auto filler = io_bufferfill_t::create();
|
2017-01-23 19:45:28 +00:00
|
|
|
pthread_t thread = pthread_self();
|
2021-01-11 20:00:33 +00:00
|
|
|
double delay = 0.50 /* seconds */;
|
2017-01-27 04:00:43 +00:00
|
|
|
iothread_perform([=]() {
|
2017-01-23 19:45:28 +00:00
|
|
|
/// Wait a while and then SIGINT the main thread.
|
|
|
|
usleep(delay * 1E6);
|
|
|
|
pthread_kill(thread, SIGINT);
|
|
|
|
});
|
2020-01-24 01:34:46 +00:00
|
|
|
eval_res_t res = parser_t::principal_parser().eval(src, io_chain_t{filler});
|
2021-02-05 01:13:15 +00:00
|
|
|
separated_buffer_t buffer = io_bufferfill_t::finish(std::move(filler));
|
2021-02-05 01:02:31 +00:00
|
|
|
if (buffer.size() != 0) {
|
2019-10-14 20:20:31 +00:00
|
|
|
err(L"Expected 0 bytes in out_buff, but instead found %lu bytes, for command %ls\n",
|
2021-02-05 01:02:31 +00:00
|
|
|
buffer.size(), src);
|
2014-01-02 20:37:50 +00:00
|
|
|
}
|
2020-01-24 01:34:46 +00:00
|
|
|
do_test(res.status.signal_exited() && res.status.signal_code() == SIGINT);
|
2014-01-02 20:37:50 +00:00
|
|
|
iothread_drain_all();
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_cancellation() {
|
2014-01-02 20:37:50 +00:00
|
|
|
say(L"Testing Ctrl-C cancellation. If this hangs, that's a bug!");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-28 08:54:29 +00:00
|
|
|
// Enable fish's signal handling here.
|
2019-05-27 21:52:48 +00:00
|
|
|
signal_set_handlers(true);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// This tests that we can correctly ctrl-C out of certain loop constructs, and that nothing gets
|
|
|
|
// printed if we do.
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Here the command substitution is an infinite loop. echo never even gets its argument, so when
|
|
|
|
// we cancel we expect no output.
|
2014-01-02 20:37:50 +00:00
|
|
|
test_1_cancellation(L"echo (while true ; echo blah ; end)");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Nasty infinite loop that doesn't actually execute anything.
|
2014-01-02 20:37:50 +00:00
|
|
|
test_1_cancellation(L"echo (while true ; end) (while true ; end) (while true ; end)");
|
|
|
|
test_1_cancellation(L"while true ; end");
|
2017-01-21 22:15:03 +00:00
|
|
|
test_1_cancellation(L"while true ; echo nothing > /dev/null; end");
|
2014-01-13 06:39:12 +00:00
|
|
|
test_1_cancellation(L"for i in (while true ; end) ; end");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2014-01-02 20:37:50 +00:00
|
|
|
signal_reset_handlers();
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Ensure that we don't think we should cancel.
|
2014-01-03 23:27:39 +00:00
|
|
|
reader_reset_interrupted();
|
2020-07-12 18:35:27 +00:00
|
|
|
signal_clear_cancel();
|
2014-01-02 20:37:50 +00:00
|
|
|
}
|
|
|
|
|
2021-07-15 19:13:53 +00:00
|
|
|
static void test_const_strlen() {
|
|
|
|
do_test(const_strlen("") == 0);
|
|
|
|
do_test(const_strlen(L"") == 0);
|
|
|
|
do_test(const_strlen("\0") == 0);
|
|
|
|
do_test(const_strlen(L"\0") == 0);
|
|
|
|
do_test(const_strlen("\0abc") == 0);
|
|
|
|
do_test(const_strlen(L"\0abc") == 0);
|
|
|
|
do_test(const_strlen("x") == 1);
|
|
|
|
do_test(const_strlen(L"x") == 1);
|
|
|
|
do_test(const_strlen("abc") == 3);
|
|
|
|
do_test(const_strlen(L"abc") == 3);
|
|
|
|
do_test(const_strlen("abc\0def") == 3);
|
|
|
|
do_test(const_strlen(L"abc\0def") == 3);
|
|
|
|
do_test(const_strlen("abcdef\0") == 6);
|
|
|
|
do_test(const_strlen(L"abcdef\0") == 6);
|
|
|
|
static_assert(const_strlen("hello") == 5, "const_strlen failure");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_const_strcmp() {
|
|
|
|
static_assert(const_strcmp("", "a") < 0, "const_strcmp failure");
|
|
|
|
static_assert(const_strcmp("a", "a") == 0, "const_strcmp failure");
|
|
|
|
static_assert(const_strcmp("a", "") > 0, "const_strcmp failure");
|
|
|
|
static_assert(const_strcmp("aa", "a") > 0, "const_strcmp failure");
|
|
|
|
static_assert(const_strcmp("a", "aa") < 0, "const_strcmp failure");
|
|
|
|
static_assert(const_strcmp("b", "aa") > 0, "const_strcmp failure");
|
|
|
|
}
|
|
|
|
|
2021-07-15 20:15:24 +00:00
|
|
|
static void test_is_sorted_by_name() {
|
|
|
|
struct named_t {
|
|
|
|
const wchar_t *name;
|
|
|
|
};
|
|
|
|
|
|
|
|
static constexpr named_t sorted[] = {
|
|
|
|
{L"a"}, {L"aa"}, {L"aaa"}, {L"aaa"}, {L"aaa"}, {L"aazz"}, {L"aazzzz"},
|
|
|
|
};
|
|
|
|
static_assert(is_sorted_by_name(sorted), "is_sorted_by_name failure");
|
2021-08-26 20:40:02 +00:00
|
|
|
do_test(get_by_sorted_name(L"", sorted) == nullptr);
|
|
|
|
do_test(get_by_sorted_name(L"nope", sorted) == nullptr);
|
|
|
|
do_test(get_by_sorted_name(L"aaaaaaaaaaa", sorted) == nullptr);
|
|
|
|
wcstring last;
|
|
|
|
for (const auto &v : sorted) {
|
|
|
|
// We have multiple items with the same name; only test the first.
|
|
|
|
if (last != v.name) {
|
|
|
|
last = v.name;
|
|
|
|
do_test(get_by_sorted_name(last, sorted) == &v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-15 20:15:24 +00:00
|
|
|
static constexpr named_t not_sorted[] = {
|
|
|
|
{L"a"}, {L"aa"}, {L"aaa"}, {L"q"}, {L"aazz"}, {L"aazz"}, {L"aazz"}, {L"aazzzz"},
|
|
|
|
};
|
|
|
|
static_assert(!is_sorted_by_name(not_sorted), "is_sorted_by_name failure");
|
|
|
|
}
|
|
|
|
|
2022-09-25 19:04:33 +00:00
|
|
|
void test_dir_iter() {
|
|
|
|
dir_iter_t baditer(L"/definitely/not/a/valid/directory/for/sure");
|
|
|
|
do_test(!baditer.valid());
|
|
|
|
do_test(baditer.error() == ENOENT || baditer.error() == EACCES);
|
|
|
|
do_test(baditer.next() == nullptr);
|
|
|
|
|
|
|
|
char t1[] = "/tmp/fish_test_dir_iter.XXXXXX";
|
|
|
|
const std::string basepathn = mkdtemp(t1);
|
|
|
|
const wcstring basepath = str2wcstring(basepathn);
|
2023-04-01 11:50:30 +00:00
|
|
|
auto makepath = [&](const wcstring &s) { return wcs2zstring(basepath + L"/" + s); };
|
2022-09-25 19:04:33 +00:00
|
|
|
|
|
|
|
const wcstring dirname = L"dir";
|
|
|
|
const wcstring regname = L"reg";
|
|
|
|
const wcstring reglinkname = L"reglink"; // link to regular file
|
|
|
|
const wcstring dirlinkname = L"dirlink"; // link to directory
|
|
|
|
const wcstring badlinkname = L"badlink"; // link to nowhere
|
|
|
|
const wcstring selflinkname = L"selflink"; // link to self
|
|
|
|
const wcstring fifoname = L"fifo";
|
2023-04-18 22:19:10 +00:00
|
|
|
const std::vector<wcstring> names = {dirname, regname, reglinkname, dirlinkname,
|
2023-04-19 08:31:47 +00:00
|
|
|
badlinkname, selflinkname, fifoname};
|
2022-09-25 19:04:33 +00:00
|
|
|
|
|
|
|
const auto is_link_name = [&](const wcstring &name) -> bool {
|
|
|
|
return contains({reglinkname, dirlinkname, badlinkname, selflinkname}, name);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Make our different file types
|
|
|
|
int ret = mkdir(makepath(dirname).c_str(), 0700);
|
|
|
|
do_test(ret == 0);
|
|
|
|
ret = open(makepath(regname).c_str(), O_CREAT | O_WRONLY, 0600);
|
|
|
|
do_test(ret >= 0);
|
|
|
|
close(ret);
|
|
|
|
ret = symlink(makepath(regname).c_str(), makepath(reglinkname).c_str());
|
|
|
|
do_test(ret == 0);
|
|
|
|
ret = symlink(makepath(dirname).c_str(), makepath(dirlinkname).c_str());
|
|
|
|
do_test(ret == 0);
|
|
|
|
ret = symlink("/this/is/an/invalid/path", makepath(badlinkname).c_str());
|
|
|
|
do_test(ret == 0);
|
|
|
|
ret = symlink(makepath(selflinkname).c_str(), makepath(selflinkname).c_str());
|
|
|
|
do_test(ret == 0);
|
|
|
|
ret = mkfifo(makepath(fifoname).c_str(), 0600);
|
|
|
|
do_test(ret == 0);
|
|
|
|
|
|
|
|
dir_iter_t iter1(basepath);
|
|
|
|
do_test(iter1.valid());
|
|
|
|
do_test(iter1.error() == 0);
|
|
|
|
size_t seen = 0;
|
|
|
|
while (const auto *entry = iter1.next()) {
|
|
|
|
seen += 1;
|
|
|
|
do_test(entry->name != L"." && entry->name != L"..");
|
|
|
|
do_test(contains(names, entry->name));
|
|
|
|
maybe_t<dir_entry_type_t> expected{};
|
|
|
|
if (entry->name == dirname) {
|
|
|
|
expected = dir_entry_type_t::dir;
|
|
|
|
} else if (entry->name == regname) {
|
|
|
|
expected = dir_entry_type_t::reg;
|
|
|
|
} else if (entry->name == reglinkname) {
|
|
|
|
expected = dir_entry_type_t::reg;
|
|
|
|
} else if (entry->name == dirlinkname) {
|
|
|
|
expected = dir_entry_type_t::dir;
|
|
|
|
} else if (entry->name == badlinkname) {
|
|
|
|
expected = none();
|
|
|
|
} else if (entry->name == selflinkname) {
|
|
|
|
expected = dir_entry_type_t::lnk;
|
|
|
|
} else if (entry->name == fifoname) {
|
|
|
|
expected = dir_entry_type_t::fifo;
|
|
|
|
} else {
|
|
|
|
err(L"Unexpected file type");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Links should never have a fast type if we are resolving them, since we cannot resolve a
|
|
|
|
// symlink from readdir.
|
|
|
|
if (is_link_name(entry->name)) {
|
|
|
|
do_test(entry->fast_type() == none());
|
|
|
|
}
|
|
|
|
// If we have a fast type, it should be correct.
|
|
|
|
do_test(entry->fast_type() == none() || entry->fast_type() == expected);
|
|
|
|
do_test(entry->check_type() == expected);
|
|
|
|
}
|
|
|
|
do_test(seen == names.size());
|
|
|
|
|
|
|
|
// Clean up.
|
|
|
|
for (const auto &name : names) {
|
|
|
|
(void)unlink(makepath(name).c_str());
|
|
|
|
}
|
|
|
|
(void)rmdir(basepathn.c_str());
|
|
|
|
}
|
|
|
|
|
2017-05-13 03:15:24 +00:00
|
|
|
static void test_utility_functions() {
|
|
|
|
say(L"Testing utility functions");
|
2021-07-15 19:13:53 +00:00
|
|
|
test_const_strlen();
|
|
|
|
test_const_strcmp();
|
2021-07-15 20:15:24 +00:00
|
|
|
test_is_sorted_by_name();
|
2013-07-17 08:35:30 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// UTF8 tests taken from Alexey Vatchenko's utf8 library. See http://www.bsdua.org/libbsdua.html.
|
2014-03-23 06:46:58 +00:00
|
|
|
static void test_utf82wchar(const char *src, size_t slen, const wchar_t *dst, size_t dlen,
|
2016-05-01 01:37:19 +00:00
|
|
|
int flags, size_t res, const char *descr) {
|
2014-03-23 06:46:58 +00:00
|
|
|
size_t size;
|
2021-08-17 22:57:52 +00:00
|
|
|
wchar_t *mem = nullptr;
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-11-21 02:25:49 +00:00
|
|
|
#if WCHAR_MAX == 0xffff
|
2016-05-01 01:37:19 +00:00
|
|
|
// Hack: if wchar is only UCS-2, and the UTF-8 input string contains astral characters, then
|
|
|
|
// tweak the expected size to 0.
|
2016-11-21 02:25:49 +00:00
|
|
|
if (src) {
|
2016-05-01 01:37:19 +00:00
|
|
|
// A UTF-8 code unit may represent an astral code point if it has 4 or more leading 1s.
|
2014-03-23 06:46:58 +00:00
|
|
|
const unsigned char astral_mask = 0xF0;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < slen; i++) {
|
|
|
|
if ((src[i] & astral_mask) == astral_mask) {
|
2016-11-05 01:40:22 +00:00
|
|
|
// Astral char. We want this conversion to fail.
|
|
|
|
res = 0; //!OCLINT(parameter reassignment)
|
2014-03-23 06:46:58 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-21 02:25:49 +00:00
|
|
|
#endif
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-31 00:31:41 +00:00
|
|
|
if (!dst) {
|
2021-08-17 22:57:52 +00:00
|
|
|
size = utf8_to_wchar(src, slen, nullptr, flags);
|
2016-05-31 00:31:41 +00:00
|
|
|
} else {
|
2014-03-23 06:46:58 +00:00
|
|
|
mem = (wchar_t *)malloc(dlen * sizeof(*mem));
|
2017-02-16 04:09:26 +00:00
|
|
|
if (!mem) {
|
2014-03-23 06:46:58 +00:00
|
|
|
err(L"u2w: %s: MALLOC FAILED\n", descr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-31 00:31:41 +00:00
|
|
|
std::wstring buff;
|
|
|
|
size = utf8_to_wchar(src, slen, &buff, flags);
|
|
|
|
std::copy(buff.begin(), buff.begin() + std::min(dlen, buff.size()), mem);
|
|
|
|
}
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-31 00:31:41 +00:00
|
|
|
if (res != size) {
|
|
|
|
err(L"u2w: %s: FAILED (rv: %lu, must be %lu)", descr, size, res);
|
2019-03-12 22:07:07 +00:00
|
|
|
} else if (mem && std::memcmp(mem, dst, size * sizeof(*mem)) != 0) {
|
2016-05-31 00:31:41 +00:00
|
|
|
err(L"u2w: %s: BROKEN", descr);
|
|
|
|
}
|
2014-03-23 06:46:58 +00:00
|
|
|
|
|
|
|
free(mem);
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Annoying variant to handle uchar to avoid narrowing conversion warnings.
|
2014-05-24 23:15:45 +00:00
|
|
|
static void test_utf82wchar(const unsigned char *usrc, size_t slen, const wchar_t *dst, size_t dlen,
|
|
|
|
int flags, size_t res, const char *descr) {
|
|
|
|
const char *src = reinterpret_cast<const char *>(usrc);
|
|
|
|
return test_utf82wchar(src, slen, dst, dlen, flags, res, descr);
|
|
|
|
}
|
|
|
|
|
2014-03-23 06:46:58 +00:00
|
|
|
static void test_wchar2utf8(const wchar_t *src, size_t slen, const char *dst, size_t dlen,
|
2016-05-01 01:37:19 +00:00
|
|
|
int flags, size_t res, const char *descr) {
|
2014-03-23 06:46:58 +00:00
|
|
|
size_t size;
|
2021-08-17 22:57:52 +00:00
|
|
|
char *mem = nullptr;
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-11-21 02:25:49 +00:00
|
|
|
#if WCHAR_MAX == 0xffff
|
2016-05-01 01:37:19 +00:00
|
|
|
// Hack: if wchar is simulating UCS-2, and the wchar_t input string contains astral characters,
|
|
|
|
// then tweak the expected size to 0.
|
2016-11-21 02:25:49 +00:00
|
|
|
if (src) {
|
2014-03-23 06:46:58 +00:00
|
|
|
const uint32_t astral_mask = 0xFFFF0000U;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < slen; i++) {
|
|
|
|
if ((src[i] & astral_mask) != 0) {
|
2016-11-05 01:40:22 +00:00
|
|
|
// Astral char. We want this conversion to fail.
|
|
|
|
res = 0; //!OCLINT(parameter reassignment)
|
2014-03-23 06:46:58 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-21 02:25:49 +00:00
|
|
|
#endif
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-31 00:31:41 +00:00
|
|
|
if (dst) {
|
2020-12-25 08:25:08 +00:00
|
|
|
// We want to pass a valid pointer to wchar_to_utf8, so allocate at least one byte.
|
|
|
|
mem = (char *)malloc(dlen + 1);
|
2017-02-16 04:09:26 +00:00
|
|
|
if (!mem) {
|
2014-03-23 06:46:58 +00:00
|
|
|
err(L"w2u: %s: MALLOC FAILED", descr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-04 13:46:15 +00:00
|
|
|
size = wchar_to_utf8(src, slen, mem, dlen, flags);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (res != size) {
|
2014-05-04 13:46:15 +00:00
|
|
|
err(L"w2u: %s: FAILED (rv: %lu, must be %lu)", descr, size, res);
|
2019-03-12 22:07:07 +00:00
|
|
|
} else if (dst && std::memcmp(mem, dst, size) != 0) {
|
2014-05-04 13:46:15 +00:00
|
|
|
err(L"w2u: %s: BROKEN", descr);
|
2014-03-23 06:46:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
free(mem);
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Annoying variant to handle uchar to avoid narrowing conversion warnings.
|
2014-05-24 23:15:45 +00:00
|
|
|
static void test_wchar2utf8(const wchar_t *src, size_t slen, const unsigned char *udst, size_t dlen,
|
2016-05-01 01:37:19 +00:00
|
|
|
int flags, size_t res, const char *descr) {
|
2014-05-24 23:15:45 +00:00
|
|
|
const char *dst = reinterpret_cast<const char *>(udst);
|
|
|
|
return test_wchar2utf8(src, slen, dst, dlen, flags, res, descr);
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_utf8() {
|
2016-05-23 02:00:13 +00:00
|
|
|
say(L"Testing utf8");
|
2014-03-23 06:46:58 +00:00
|
|
|
wchar_t w1[] = {0x54, 0x65, 0x73, 0x74};
|
|
|
|
wchar_t w2[] = {0x0422, 0x0435, 0x0441, 0x0442};
|
|
|
|
wchar_t w3[] = {0x800, 0x1e80, 0x98c4, 0x9910, 0xff00};
|
2016-05-23 02:00:13 +00:00
|
|
|
wchar_t wm[] = {0x41, 0x0441, 0x3042, 0xff67, 0x9b0d};
|
|
|
|
wchar_t wb2[] = {0xd800, 0xda00, 0x41, 0xdfff, 0x0a};
|
|
|
|
wchar_t wbom[] = {0xfeff, 0x41, 0x0a};
|
2014-03-23 06:46:58 +00:00
|
|
|
wchar_t wbom2[] = {0x41, 0xa};
|
2016-05-23 02:00:13 +00:00
|
|
|
wchar_t wbom22[] = {0xfeff, 0x41, 0x0a};
|
2014-05-24 23:15:45 +00:00
|
|
|
unsigned char u1[] = {0x54, 0x65, 0x73, 0x74};
|
|
|
|
unsigned char u2[] = {0xd0, 0xa2, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82};
|
2016-05-01 01:37:19 +00:00
|
|
|
unsigned char u3[] = {0xe0, 0xa0, 0x80, 0xe1, 0xba, 0x80, 0xe9, 0xa3,
|
|
|
|
0x84, 0xe9, 0xa4, 0x90, 0xef, 0xbc, 0x80};
|
2016-05-23 02:00:13 +00:00
|
|
|
unsigned char um[] = {0x41, 0xd1, 0x81, 0xe3, 0x81, 0x82, 0xef, 0xbd, 0xa7, 0xe9, 0xac, 0x8d};
|
2014-05-24 23:15:45 +00:00
|
|
|
unsigned char uc080[] = {0xc0, 0x80};
|
2016-05-23 02:00:13 +00:00
|
|
|
unsigned char ub2[] = {0xed, 0xa1, 0x8c, 0xed, 0xbe, 0xb4, 0x0a};
|
2014-05-24 23:15:45 +00:00
|
|
|
unsigned char ubom[] = {0x41, 0xa};
|
2016-05-23 02:00:13 +00:00
|
|
|
unsigned char ubom2[] = {0xef, 0xbb, 0xbf, 0x41, 0x0a};
|
2016-11-21 02:05:34 +00:00
|
|
|
#if WCHAR_MAX != 0xffff
|
|
|
|
wchar_t w4[] = {0x15555, 0xf7777, 0x0a};
|
|
|
|
wchar_t wb[] = {(wchar_t)-2, 0xa, (wchar_t)0xffffffff, 0x0441};
|
|
|
|
wchar_t wb1[] = {0x0a, 0x0422};
|
|
|
|
unsigned char u4[] = {0xf0, 0x95, 0x95, 0x95, 0xf3, 0xb7, 0x9d, 0xb7, 0x0a};
|
|
|
|
unsigned char ub[] = {0xa, 0xd1, 0x81};
|
|
|
|
unsigned char ub1[] = {0xa, 0xff, 0xd0, 0xa2, 0xfe, 0x8f, 0xe0, 0x80};
|
|
|
|
#endif
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// UTF-8 -> UCS-4 string.
|
|
|
|
test_utf82wchar(ubom2, sizeof(ubom2), wbom2, sizeof(wbom2) / sizeof(*wbom2), UTF8_SKIP_BOM,
|
2016-05-23 02:00:13 +00:00
|
|
|
sizeof(wbom2) / sizeof(*wbom2), "ubom2 skip BOM");
|
2016-05-01 01:37:19 +00:00
|
|
|
test_utf82wchar(ubom2, sizeof(ubom2), wbom22, sizeof(wbom22) / sizeof(*wbom22), 0,
|
2016-05-23 02:00:13 +00:00
|
|
|
sizeof(wbom22) / sizeof(*wbom22), "ubom2 BOM");
|
2021-08-17 22:57:52 +00:00
|
|
|
test_utf82wchar(uc080, sizeof(uc080), nullptr, 0, 0, 0, "uc080 c0 80 - forbitten by rfc3629");
|
|
|
|
test_utf82wchar(ub2, sizeof(ub2), nullptr, 0, 0, 3, "ub2 resulted in forbitten wchars (len)");
|
2014-03-23 06:46:58 +00:00
|
|
|
test_utf82wchar(ub2, sizeof(ub2), wb2, sizeof(wb2) / sizeof(*wb2), 0, 0,
|
2016-05-23 02:00:13 +00:00
|
|
|
"ub2 resulted in forbitten wchars");
|
2016-05-01 01:37:19 +00:00
|
|
|
test_utf82wchar(ub2, sizeof(ub2), L"\x0a", 1, UTF8_IGNORE_ERROR, 1,
|
2016-05-23 02:00:13 +00:00
|
|
|
"ub2 resulted in ignored forbitten wchars");
|
2016-05-01 01:37:19 +00:00
|
|
|
test_utf82wchar(u1, sizeof(u1), w1, sizeof(w1) / sizeof(*w1), 0, sizeof(w1) / sizeof(*w1),
|
2016-05-23 02:00:13 +00:00
|
|
|
"u1/w1 1 octet chars");
|
2016-05-01 01:37:19 +00:00
|
|
|
test_utf82wchar(u2, sizeof(u2), w2, sizeof(w2) / sizeof(*w2), 0, sizeof(w2) / sizeof(*w2),
|
2016-05-23 02:00:13 +00:00
|
|
|
"u2/w2 2 octets chars");
|
2016-05-01 01:37:19 +00:00
|
|
|
test_utf82wchar(u3, sizeof(u3), w3, sizeof(w3) / sizeof(*w3), 0, sizeof(w3) / sizeof(*w3),
|
2016-05-23 02:00:13 +00:00
|
|
|
"u3/w3 3 octets chars");
|
2021-08-17 22:57:52 +00:00
|
|
|
test_utf82wchar("\xff", 1, nullptr, 0, 0, 0, "broken utf-8 0xff symbol");
|
|
|
|
test_utf82wchar("\xfe", 1, nullptr, 0, 0, 0, "broken utf-8 0xfe symbol");
|
|
|
|
test_utf82wchar("\x8f", 1, nullptr, 0, 0, 0, "broken utf-8, start from 10 higher bits");
|
|
|
|
test_utf82wchar((const char *)nullptr, 0, nullptr, 0, 0, 0, "invalid params, all 0");
|
|
|
|
test_utf82wchar(u1, 0, nullptr, 0, 0, 0, "invalid params, src buf not NULL");
|
|
|
|
test_utf82wchar((const char *)nullptr, 10, nullptr, 0, 0, 0,
|
|
|
|
"invalid params, src length is not 0");
|
2016-05-01 01:37:19 +00:00
|
|
|
|
|
|
|
// UCS-4 -> UTF-8 string.
|
2021-08-17 22:57:52 +00:00
|
|
|
const char *const nullc = nullptr;
|
2016-05-01 01:37:19 +00:00
|
|
|
test_wchar2utf8(wbom, sizeof(wbom) / sizeof(*wbom), ubom, sizeof(ubom), UTF8_SKIP_BOM,
|
|
|
|
sizeof(ubom), "BOM");
|
|
|
|
test_wchar2utf8(wb2, sizeof(wb2) / sizeof(*wb2), nullc, 0, 0, 0, "prohibited wchars");
|
|
|
|
test_wchar2utf8(wb2, sizeof(wb2) / sizeof(*wb2), nullc, 0, UTF8_IGNORE_ERROR, 2,
|
|
|
|
"ignore prohibited wchars");
|
2016-05-23 02:00:13 +00:00
|
|
|
test_wchar2utf8(w1, sizeof(w1) / sizeof(*w1), u1, sizeof(u1), 0, sizeof(u1),
|
|
|
|
"w1/u1 1 octet chars");
|
|
|
|
test_wchar2utf8(w2, sizeof(w2) / sizeof(*w2), u2, sizeof(u2), 0, sizeof(u2),
|
|
|
|
"w2/u2 2 octets chars");
|
|
|
|
test_wchar2utf8(w3, sizeof(w3) / sizeof(*w3), u3, sizeof(u3), 0, sizeof(u3),
|
|
|
|
"w3/u3 3 octets chars");
|
2021-08-17 22:57:52 +00:00
|
|
|
test_wchar2utf8(nullptr, 0, nullc, 0, 0, 0, "invalid params, all 0");
|
2016-05-23 02:00:13 +00:00
|
|
|
test_wchar2utf8(w1, 0, nullc, 0, 0, 0, "invalid params, src buf not NULL");
|
|
|
|
test_wchar2utf8(w1, sizeof(w1) / sizeof(*w1), u1, 0, 0, 0, "invalid params, dst is not NULL");
|
2021-08-17 22:57:52 +00:00
|
|
|
test_wchar2utf8(nullptr, 10, nullc, 0, 0, 0, "invalid params, src length is not 0");
|
2016-05-23 02:00:13 +00:00
|
|
|
|
2016-11-21 02:05:34 +00:00
|
|
|
test_wchar2utf8(wm, sizeof(wm) / sizeof(*wm), um, sizeof(um), 0, sizeof(um),
|
|
|
|
"wm/um mixed languages");
|
|
|
|
test_wchar2utf8(wm, sizeof(wm) / sizeof(*wm), um, sizeof(um) - 1, 0, 0, "wm/um boundaries -1");
|
|
|
|
test_wchar2utf8(wm, sizeof(wm) / sizeof(*wm), um, sizeof(um) + 1, 0, sizeof(um),
|
|
|
|
"wm/um boundaries +1");
|
|
|
|
test_wchar2utf8(wm, sizeof(wm) / sizeof(*wm), nullc, 0, 0, sizeof(um),
|
|
|
|
"wm/um calculate length");
|
|
|
|
test_utf82wchar(um, sizeof(um), wm, sizeof(wm) / sizeof(*wm), 0, sizeof(wm) / sizeof(*wm),
|
|
|
|
"um/wm mixed languages");
|
|
|
|
test_utf82wchar(um, sizeof(um), wm, sizeof(wm) / sizeof(*wm) + 1, 0, sizeof(wm) / sizeof(*wm),
|
|
|
|
"um/wm boundaries +1");
|
2021-08-17 22:57:52 +00:00
|
|
|
test_utf82wchar(um, sizeof(um), nullptr, 0, 0, sizeof(wm) / sizeof(*wm),
|
|
|
|
"um/wm calculate length");
|
2016-11-21 02:05:34 +00:00
|
|
|
|
2016-11-23 04:24:03 +00:00
|
|
|
// The following tests won't pass on systems (e.g., Cygwin) where sizeof wchar_t is 2. That's
|
|
|
|
// due to several reasons but the primary one is that narrowing conversions of literals assigned
|
|
|
|
// to the wchar_t arrays above don't result in values that will be treated as errors by the
|
|
|
|
// conversion functions.
|
2016-11-21 02:05:34 +00:00
|
|
|
#if WCHAR_MAX != 0xffff
|
2016-05-23 02:00:13 +00:00
|
|
|
test_utf82wchar(u4, sizeof(u4), w4, sizeof(w4) / sizeof(*w4), 0, sizeof(w4) / sizeof(*w4),
|
|
|
|
"u4/w4 4 octets chars");
|
|
|
|
test_wchar2utf8(w4, sizeof(w4) / sizeof(*w4), u4, sizeof(u4), 0, sizeof(u4),
|
|
|
|
"w4/u4 4 octets chars");
|
|
|
|
test_wchar2utf8(wb, sizeof(wb) / sizeof(*wb), ub, sizeof(ub), 0, 0, "wb/ub bad chars");
|
2016-05-01 01:37:19 +00:00
|
|
|
test_wchar2utf8(wb, sizeof(wb) / sizeof(*wb), ub, sizeof(ub), UTF8_IGNORE_ERROR, sizeof(ub),
|
2016-05-23 02:00:13 +00:00
|
|
|
"wb/ub ignore bad chars");
|
|
|
|
test_wchar2utf8(wb, sizeof(wb) / sizeof(*wb), nullc, 0, 0, 0,
|
|
|
|
"wb calculate length of bad chars");
|
2016-05-01 01:37:19 +00:00
|
|
|
test_wchar2utf8(wb, sizeof(wb) / sizeof(*wb), nullc, 0, UTF8_IGNORE_ERROR, sizeof(ub),
|
2014-03-23 06:46:58 +00:00
|
|
|
"calculate length, ignore bad chars");
|
2016-05-23 02:00:13 +00:00
|
|
|
test_utf82wchar(ub1, sizeof(ub1), wb1, sizeof(wb1) / sizeof(*wb1), UTF8_IGNORE_ERROR,
|
|
|
|
sizeof(wb1) / sizeof(*wb1), "ub1/wb1 ignore bad chars");
|
2021-08-17 22:57:52 +00:00
|
|
|
test_utf82wchar(ub1, sizeof(ub1), nullptr, 0, 0, 0, "ub1 calculate length of bad chars");
|
|
|
|
test_utf82wchar(ub1, sizeof(ub1), nullptr, 0, UTF8_IGNORE_ERROR, sizeof(wb1) / sizeof(*wb1),
|
2016-05-23 02:00:13 +00:00
|
|
|
"ub1 calculate length, ignore bad chars");
|
2016-11-21 02:05:34 +00:00
|
|
|
#endif
|
2014-03-23 06:46:58 +00:00
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_escape_sequences() {
|
2016-05-23 02:00:13 +00:00
|
|
|
say(L"Testing escape_sequences");
|
2020-05-30 21:59:35 +00:00
|
|
|
layout_cache_t lc;
|
|
|
|
if (lc.escape_code_length(L"") != 0)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
2020-05-30 21:59:35 +00:00
|
|
|
if (lc.escape_code_length(L"abcd") != 0)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
2020-05-30 21:59:35 +00:00
|
|
|
if (lc.escape_code_length(L"\x1B[2J") != 4)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
2021-02-13 06:52:01 +00:00
|
|
|
if (lc.escape_code_length(L"\x1B[38;5;123mABC") != strlen("\x1B[38;5;123m"))
|
2020-05-30 21:59:35 +00:00
|
|
|
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
|
|
|
if (lc.escape_code_length(L"\x1B@") != 2)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
|
|
|
|
|
|
|
// iTerm2 escape sequences.
|
2020-05-30 21:59:35 +00:00
|
|
|
if (lc.escape_code_length(L"\x1B]50;CurrentDir=test/foo\x07NOT_PART_OF_SEQUENCE") != 25)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
2020-05-30 21:59:35 +00:00
|
|
|
if (lc.escape_code_length(L"\x1B]50;SetMark\x07NOT_PART_OF_SEQUENCE") != 13)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
2020-05-30 21:59:35 +00:00
|
|
|
if (lc.escape_code_length(L"\x1B]6;1;bg;red;brightness;255\x07NOT_PART_OF_SEQUENCE") != 28)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
2020-05-30 21:59:35 +00:00
|
|
|
if (lc.escape_code_length(L"\x1B]Pg4040ff\x1B\\NOT_PART_OF_SEQUENCE") != 12)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
2020-05-30 21:59:35 +00:00
|
|
|
if (lc.escape_code_length(L"\x1B]blahblahblah\x1B\\") != 16)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
2020-05-30 21:59:35 +00:00
|
|
|
if (lc.escape_code_length(L"\x1B]blahblahblah\x07") != 15)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
|
|
|
}
|
|
|
|
|
2021-09-27 22:41:53 +00:00
|
|
|
class test_lru_t : public lru_cache_t<int> {
|
2016-05-01 01:37:19 +00:00
|
|
|
public:
|
2017-01-30 18:25:17 +00:00
|
|
|
static constexpr size_t test_capacity = 16;
|
2020-09-28 00:36:23 +00:00
|
|
|
using value_type = std::pair<wcstring, int>;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2021-09-27 22:41:53 +00:00
|
|
|
test_lru_t() : lru_cache_t<int>(test_capacity) {}
|
2017-01-30 18:25:17 +00:00
|
|
|
|
|
|
|
std::vector<value_type> values() const {
|
|
|
|
std::vector<value_type> result;
|
2020-07-12 18:41:06 +00:00
|
|
|
for (auto p : *this) {
|
2017-01-30 18:25:17 +00:00
|
|
|
result.push_back(p);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<int> ints() const {
|
|
|
|
std::vector<int> result;
|
2020-07-12 18:41:06 +00:00
|
|
|
for (auto p : *this) {
|
2017-01-30 18:25:17 +00:00
|
|
|
result.push_back(p.second);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2012-01-27 19:43:45 +00:00
|
|
|
};
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_lru() {
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Testing LRU cache");
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2012-01-27 19:43:45 +00:00
|
|
|
test_lru_t cache;
|
2017-01-27 20:18:16 +00:00
|
|
|
std::vector<std::pair<wcstring, int>> expected_evicted;
|
2017-01-30 18:25:17 +00:00
|
|
|
std::vector<std::pair<wcstring, int>> expected_values;
|
2017-01-27 20:18:16 +00:00
|
|
|
int total_nodes = 20;
|
|
|
|
for (int i = 0; i < total_nodes; i++) {
|
|
|
|
do_test(cache.size() == size_t(std::min(i, 16)));
|
2017-01-30 18:25:17 +00:00
|
|
|
do_test(cache.values() == expected_values);
|
2021-08-17 09:20:28 +00:00
|
|
|
if (i < 4) expected_evicted.emplace_back(to_string(i), i);
|
2016-05-01 01:37:19 +00:00
|
|
|
// Adding the node the first time should work, and subsequent times should fail.
|
2017-01-27 20:18:16 +00:00
|
|
|
do_test(cache.insert(to_string(i), i));
|
2017-01-30 01:56:03 +00:00
|
|
|
do_test(!cache.insert(to_string(i), i + 1));
|
2017-01-30 18:25:17 +00:00
|
|
|
|
2021-08-17 09:20:28 +00:00
|
|
|
expected_values.emplace_back(to_string(i), i);
|
2017-01-30 18:25:17 +00:00
|
|
|
while (expected_values.size() > test_lru_t::test_capacity) {
|
|
|
|
expected_values.erase(expected_values.begin());
|
|
|
|
}
|
2017-01-30 20:59:01 +00:00
|
|
|
cache.check_sanity();
|
2012-01-27 19:43:45 +00:00
|
|
|
}
|
2017-01-30 18:25:17 +00:00
|
|
|
do_test(cache.values() == expected_values);
|
2017-01-30 20:59:01 +00:00
|
|
|
cache.check_sanity();
|
2017-01-30 18:25:17 +00:00
|
|
|
|
|
|
|
// Stable-sort ints in reverse order
|
|
|
|
// This a/2 check ensures that some different ints compare the same
|
|
|
|
// It also gives us a different order than we started with
|
2017-02-08 05:52:35 +00:00
|
|
|
auto comparer = [](int a, int b) { return a / 2 > b / 2; };
|
2017-01-30 18:25:17 +00:00
|
|
|
std::vector<int> ints = cache.ints();
|
|
|
|
std::stable_sort(ints.begin(), ints.end(), comparer);
|
|
|
|
|
|
|
|
cache.stable_sort(comparer);
|
|
|
|
std::vector<int> new_ints = cache.ints();
|
|
|
|
if (new_ints != ints) {
|
|
|
|
auto commajoin = [](const std::vector<int> &vs) {
|
|
|
|
wcstring ret;
|
|
|
|
for (int v : vs) {
|
2019-02-19 07:19:57 +00:00
|
|
|
append_format(ret, L"%d,", v);
|
2017-01-30 18:25:17 +00:00
|
|
|
}
|
2017-02-08 05:52:35 +00:00
|
|
|
if (!ret.empty()) ret.pop_back();
|
2017-01-30 18:25:17 +00:00
|
|
|
return ret;
|
|
|
|
};
|
2017-02-08 05:52:35 +00:00
|
|
|
err(L"LRU stable sort failed. Expected %ls, got %ls\n", commajoin(new_ints).c_str(),
|
|
|
|
commajoin(ints).c_str());
|
2017-01-30 18:25:17 +00:00
|
|
|
}
|
|
|
|
|
2012-01-27 19:43:45 +00:00
|
|
|
cache.evict_all_nodes();
|
2021-09-27 22:41:53 +00:00
|
|
|
do_test(cache.size() == 0);
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2020-12-30 08:40:56 +00:00
|
|
|
/// An environment built around an std::map.
|
|
|
|
struct test_environment_t : public environment_t {
|
|
|
|
std::map<wcstring, wcstring> vars;
|
2018-09-22 04:52:47 +00:00
|
|
|
|
2021-08-17 20:42:00 +00:00
|
|
|
maybe_t<env_var_t> get(const wcstring &key,
|
|
|
|
env_mode_flags_t mode = ENV_DEFAULT) const override {
|
2019-05-29 19:11:08 +00:00
|
|
|
UNUSED(mode);
|
2020-12-30 08:40:56 +00:00
|
|
|
auto iter = vars.find(key);
|
|
|
|
if (iter != vars.end()) {
|
|
|
|
return env_var_t(iter->second, ENV_DEFAULT);
|
|
|
|
}
|
|
|
|
return none();
|
|
|
|
}
|
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> get_names(env_mode_flags_t flags) const override {
|
2020-12-30 08:40:56 +00:00
|
|
|
UNUSED(flags);
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> result;
|
2020-12-30 08:40:56 +00:00
|
|
|
for (const auto &kv : vars) {
|
|
|
|
result.push_back(kv.first);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/// A test environment that knows about PWD.
|
|
|
|
struct pwd_environment_t : public test_environment_t {
|
2021-08-17 20:42:00 +00:00
|
|
|
maybe_t<env_var_t> get(const wcstring &key,
|
|
|
|
env_mode_flags_t mode = ENV_DEFAULT) const override {
|
2018-09-11 05:29:52 +00:00
|
|
|
if (key == L"PWD") {
|
|
|
|
return env_var_t{wgetcwd(), 0};
|
|
|
|
}
|
2020-12-30 08:40:56 +00:00
|
|
|
return test_environment_t::get(key, mode);
|
2018-09-11 05:29:52 +00:00
|
|
|
}
|
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> get_names(env_mode_flags_t flags) const override {
|
2020-12-30 08:40:56 +00:00
|
|
|
auto res = test_environment_t::get_names(flags);
|
|
|
|
res.clear();
|
|
|
|
if (std::count(res.begin(), res.end(), L"PWD") == 0) {
|
2021-08-17 20:36:52 +00:00
|
|
|
res.emplace_back(L"PWD");
|
2020-12-30 08:40:56 +00:00
|
|
|
}
|
|
|
|
return res;
|
2019-06-04 03:30:48 +00:00
|
|
|
}
|
2018-09-11 05:29:52 +00:00
|
|
|
};
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Perform parameter expansion and test if the output equals the zero-terminated parameter list
|
|
|
|
/// supplied.
|
|
|
|
///
|
|
|
|
/// \param in the string to expand
|
|
|
|
/// \param flags the flags to send to expand_string
|
|
|
|
/// \param ... A zero-terminated parameter list of values to test.
|
|
|
|
/// After the zero terminator comes one more arg, a string, which is the error
|
|
|
|
/// message to print if the test fails.
|
|
|
|
static bool expand_test(const wchar_t *in, expand_flags_t flags, ...) {
|
2020-01-16 00:13:41 +00:00
|
|
|
completion_list_t output;
|
2012-11-19 00:30:30 +00:00
|
|
|
va_list va;
|
2016-05-01 01:37:19 +00:00
|
|
|
bool res = true;
|
2012-11-19 00:30:30 +00:00
|
|
|
wchar_t *arg;
|
2023-02-05 08:35:06 +00:00
|
|
|
auto errors = new_parse_error_list();
|
2020-01-16 01:14:47 +00:00
|
|
|
pwd_environment_t pwd{};
|
|
|
|
operation_context_t ctx{parser_t::principal_parser().shared(), pwd, no_cancel};
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2023-02-05 08:35:06 +00:00
|
|
|
if (expand_string(in, &output, flags, ctx, &*errors) == expand_result_t::error) {
|
|
|
|
if (errors->empty()) {
|
2014-09-26 02:45:54 +00:00
|
|
|
err(L"Bug: Parse error reported but no error text found.");
|
2016-05-01 01:37:19 +00:00
|
|
|
} else {
|
2023-02-05 08:35:06 +00:00
|
|
|
err(L"%ls", errors->at(0)->describe(in, ctx.parser->is_interactive())->c_str());
|
2014-09-26 02:45:54 +00:00
|
|
|
}
|
|
|
|
return false;
|
2012-10-16 01:16:47 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> expected;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2014-09-26 02:45:54 +00:00
|
|
|
va_start(va, flags);
|
2021-08-17 22:57:52 +00:00
|
|
|
while ((arg = va_arg(va, wchar_t *)) != nullptr) {
|
2021-08-17 09:20:28 +00:00
|
|
|
expected.emplace_back(arg);
|
2014-09-26 02:45:54 +00:00
|
|
|
}
|
|
|
|
va_end(va);
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2015-08-08 22:25:07 +00:00
|
|
|
std::set<wcstring> remaining(expected.begin(), expected.end());
|
2020-01-16 00:13:41 +00:00
|
|
|
completion_list_t::const_iterator out_it = output.begin(), out_end = output.end();
|
2016-05-01 01:37:19 +00:00
|
|
|
for (; out_it != out_end; ++out_it) {
|
|
|
|
if (!remaining.erase(out_it->completion)) {
|
2014-09-26 02:45:54 +00:00
|
|
|
res = false;
|
2014-09-26 01:51:03 +00:00
|
|
|
break;
|
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!remaining.empty()) {
|
2015-08-08 22:25:07 +00:00
|
|
|
res = false;
|
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!res) {
|
2016-11-05 01:40:22 +00:00
|
|
|
arg = va_arg(va, wchar_t *);
|
|
|
|
if (arg) {
|
2014-09-26 02:45:54 +00:00
|
|
|
wcstring msg = L"Expected [";
|
|
|
|
bool first = true;
|
2018-09-16 22:49:18 +00:00
|
|
|
for (const wcstring &exp : expected) {
|
2014-09-26 02:45:54 +00:00
|
|
|
if (!first) msg += L", ";
|
|
|
|
first = false;
|
|
|
|
msg += '"';
|
2018-09-16 22:49:18 +00:00
|
|
|
msg += exp;
|
2014-09-26 02:45:54 +00:00
|
|
|
msg += '"';
|
|
|
|
}
|
|
|
|
msg += L"], found [";
|
|
|
|
first = true;
|
2020-06-30 22:29:06 +00:00
|
|
|
for (const auto &completion : output) {
|
2014-09-26 02:45:54 +00:00
|
|
|
if (!first) msg += L", ";
|
|
|
|
first = false;
|
|
|
|
msg += '"';
|
2020-06-30 22:29:06 +00:00
|
|
|
msg += completion.completion;
|
2014-09-26 02:45:54 +00:00
|
|
|
msg += '"';
|
|
|
|
}
|
|
|
|
msg += L"]";
|
|
|
|
err(L"%ls\n%ls", arg, msg.c_str());
|
2014-09-26 01:51:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
va_end(va);
|
|
|
|
|
2012-11-19 00:30:30 +00:00
|
|
|
return res;
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Test globbing and other parameter expansion.
|
|
|
|
static void test_expand() {
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Testing parameter expansion");
|
2019-04-25 18:22:17 +00:00
|
|
|
const expand_flags_t noflags{};
|
2021-12-30 09:15:12 +00:00
|
|
|
const wchar_t *const wnull = nullptr;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2021-12-30 09:15:12 +00:00
|
|
|
expand_test(L"foo", noflags, L"foo", wnull, L"Strings do not expand to themselves");
|
|
|
|
expand_test(L"a{b,c,d}e", noflags, L"abe", L"ace", L"ade", wnull,
|
|
|
|
L"Bracket expansion is broken");
|
|
|
|
expand_test(L"a*", expand_flag::skip_wildcards, L"a*", wnull,
|
|
|
|
L"Cannot skip wildcard expansion");
|
|
|
|
expand_test(L"/bin/l\\0", expand_flag::for_completions, wnull,
|
2014-09-26 01:51:03 +00:00
|
|
|
L"Failed to handle null escape in expansion");
|
2021-12-30 09:15:12 +00:00
|
|
|
expand_test(L"foo\\$bar", expand_flag::skip_variables, L"foo$bar", wnull,
|
2014-09-26 01:51:03 +00:00
|
|
|
L"Failed to handle dollar sign in variable-skipping expansion");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-10-02 00:58:15 +00:00
|
|
|
// bb
|
2016-05-01 01:37:19 +00:00
|
|
|
// x
|
|
|
|
// bar
|
|
|
|
// baz
|
|
|
|
// xxx
|
|
|
|
// yyy
|
|
|
|
// bax
|
|
|
|
// xxx
|
|
|
|
// lol
|
|
|
|
// nub
|
|
|
|
// q
|
|
|
|
// .foo
|
2016-10-02 00:58:15 +00:00
|
|
|
// aaa
|
|
|
|
// aaa2
|
|
|
|
// x
|
2017-04-09 04:05:18 +00:00
|
|
|
if (system("mkdir -p test/fish_expand_test/")) err(L"mkdir failed");
|
|
|
|
if (system("mkdir -p test/fish_expand_test/bb/")) err(L"mkdir failed");
|
|
|
|
if (system("mkdir -p test/fish_expand_test/baz/")) err(L"mkdir failed");
|
|
|
|
if (system("mkdir -p test/fish_expand_test/bax/")) err(L"mkdir failed");
|
|
|
|
if (system("mkdir -p test/fish_expand_test/lol/nub/")) err(L"mkdir failed");
|
|
|
|
if (system("mkdir -p test/fish_expand_test/aaa/")) err(L"mkdir failed");
|
|
|
|
if (system("mkdir -p test/fish_expand_test/aaa2/")) err(L"mkdir failed");
|
|
|
|
if (system("touch test/fish_expand_test/.foo")) err(L"touch failed");
|
|
|
|
if (system("touch test/fish_expand_test/bb/x")) err(L"touch failed");
|
|
|
|
if (system("touch test/fish_expand_test/bar")) err(L"touch failed");
|
|
|
|
if (system("touch test/fish_expand_test/bax/xxx")) err(L"touch failed");
|
|
|
|
if (system("touch test/fish_expand_test/baz/xxx")) err(L"touch failed");
|
|
|
|
if (system("touch test/fish_expand_test/baz/yyy")) err(L"touch failed");
|
|
|
|
if (system("touch test/fish_expand_test/lol/nub/q")) err(L"touch failed");
|
|
|
|
if (system("touch test/fish_expand_test/aaa2/x")) err(L"touch failed");
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// This is checking that .* does NOT match . and ..
|
|
|
|
// (https://github.com/fish-shell/fish-shell/issues/270). But it does have to match literal
|
|
|
|
// components (e.g. "./*" has to match the same as "*".
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/.*", noflags, L"test/fish_expand_test/.foo", wnull,
|
2014-09-26 01:51:03 +00:00
|
|
|
L"Expansion not correctly handling dotfiles");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/./.*", noflags, L"test/fish_expand_test/./.foo", wnull,
|
2014-09-26 01:51:03 +00:00
|
|
|
L"Expansion not correctly handling literal path components in dotfiles");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/*/xxx", noflags, L"test/fish_expand_test/bax/xxx",
|
2017-04-09 04:05:18 +00:00
|
|
|
L"test/fish_expand_test/baz/xxx", wnull, L"Glob did the wrong thing 1");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/*z/xxx", noflags, L"test/fish_expand_test/baz/xxx", wnull,
|
2015-08-07 20:28:47 +00:00
|
|
|
L"Glob did the wrong thing 2");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/**z/xxx", noflags, L"test/fish_expand_test/baz/xxx", wnull,
|
2015-08-07 20:28:47 +00:00
|
|
|
L"Glob did the wrong thing 3");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test////baz/xxx", noflags, L"test/fish_expand_test////baz/xxx",
|
|
|
|
wnull, L"Glob did the wrong thing 3");
|
2016-07-09 22:27:10 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/b**", noflags, L"test/fish_expand_test/bb",
|
2017-04-09 04:05:18 +00:00
|
|
|
L"test/fish_expand_test/bb/x", L"test/fish_expand_test/bar",
|
|
|
|
L"test/fish_expand_test/bax", L"test/fish_expand_test/bax/xxx",
|
|
|
|
L"test/fish_expand_test/baz", L"test/fish_expand_test/baz/xxx",
|
|
|
|
L"test/fish_expand_test/baz/yyy", wnull, L"Glob did the wrong thing 4");
|
2016-05-01 01:37:19 +00:00
|
|
|
|
|
|
|
// A trailing slash should only produce directories.
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/b*/", noflags, L"test/fish_expand_test/bb/",
|
2017-04-09 04:05:18 +00:00
|
|
|
L"test/fish_expand_test/baz/", L"test/fish_expand_test/bax/", wnull,
|
2015-09-27 20:32:12 +00:00
|
|
|
L"Glob did the wrong thing 5");
|
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/b**/", noflags, L"test/fish_expand_test/bb/",
|
2017-04-09 04:05:18 +00:00
|
|
|
L"test/fish_expand_test/baz/", L"test/fish_expand_test/bax/", wnull,
|
2015-09-27 20:32:12 +00:00
|
|
|
L"Glob did the wrong thing 6");
|
2016-05-01 01:37:19 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/**/q", noflags, L"test/fish_expand_test/lol/nub/q", wnull,
|
2015-09-28 00:19:17 +00:00
|
|
|
L"Glob did the wrong thing 7");
|
2016-05-01 01:37:19 +00:00
|
|
|
|
2019-04-25 18:34:49 +00:00
|
|
|
expand_test(L"test/fish_expand_test/BA", expand_flag::for_completions,
|
2019-04-25 18:22:17 +00:00
|
|
|
L"test/fish_expand_test/bar", L"test/fish_expand_test/bax/",
|
|
|
|
L"test/fish_expand_test/baz/", wnull, L"Case insensitive test did the wrong thing");
|
2015-08-03 00:58:37 +00:00
|
|
|
|
2019-04-25 18:34:49 +00:00
|
|
|
expand_test(L"test/fish_expand_test/BA", expand_flag::for_completions,
|
2019-04-25 18:22:17 +00:00
|
|
|
L"test/fish_expand_test/bar", L"test/fish_expand_test/bax/",
|
|
|
|
L"test/fish_expand_test/baz/", wnull, L"Case insensitive test did the wrong thing");
|
2015-08-07 20:28:47 +00:00
|
|
|
|
2019-04-25 18:34:49 +00:00
|
|
|
expand_test(L"test/fish_expand_test/bb/yyy", expand_flag::for_completions,
|
2016-05-01 01:37:19 +00:00
|
|
|
/* nothing! */ wnull, L"Wrong fuzzy matching 1");
|
2014-09-25 05:01:33 +00:00
|
|
|
|
2019-04-25 18:34:49 +00:00
|
|
|
expand_test(L"test/fish_expand_test/bb/x",
|
|
|
|
expand_flags_t{expand_flag::for_completions, expand_flag::fuzzy_match}, L"",
|
|
|
|
wnull, // we just expect the empty string since this is an exact match
|
|
|
|
L"Wrong fuzzy matching 2");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Some vswprintfs refuse to append ANY_STRING in a format specifiers, so don't use
|
|
|
|
// format_string here.
|
2019-04-25 18:34:49 +00:00
|
|
|
const expand_flags_t fuzzy_comp{expand_flag::for_completions, expand_flag::fuzzy_match};
|
2015-09-12 23:31:28 +00:00
|
|
|
const wcstring any_str_str(1, ANY_STRING);
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/b/xx*", fuzzy_comp,
|
2017-04-09 04:05:18 +00:00
|
|
|
(L"test/fish_expand_test/bax/xx" + any_str_str).c_str(),
|
|
|
|
(L"test/fish_expand_test/baz/xx" + any_str_str).c_str(), wnull,
|
2015-08-07 20:28:47 +00:00
|
|
|
L"Wrong fuzzy matching 3");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/b/yyy", fuzzy_comp, L"test/fish_expand_test/baz/yyy", wnull,
|
|
|
|
L"Wrong fuzzy matching 4");
|
2016-10-05 03:06:14 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/aa/x", fuzzy_comp, L"test/fish_expand_test/aaa2/x", wnull,
|
|
|
|
L"Wrong fuzzy matching 5");
|
2016-10-05 03:06:14 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"test/fish_expand_test/aaa/x", fuzzy_comp, wnull,
|
2016-10-05 03:06:14 +00:00
|
|
|
L"Wrong fuzzy matching 6 - shouldn't remove valid directory names (#3211)");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
if (!expand_test(L"test/fish_expand_test/.*", noflags, L"test/fish_expand_test/.foo", 0)) {
|
2014-09-26 05:18:36 +00:00
|
|
|
err(L"Expansion not correctly handling dotfiles");
|
2014-09-26 01:51:03 +00:00
|
|
|
}
|
2019-04-25 18:22:17 +00:00
|
|
|
if (!expand_test(L"test/fish_expand_test/./.*", noflags, L"test/fish_expand_test/./.foo", 0)) {
|
2014-09-26 05:18:36 +00:00
|
|
|
err(L"Expansion not correctly handling literal path components in dotfiles");
|
2014-09-25 05:01:33 +00:00
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2017-04-09 04:05:18 +00:00
|
|
|
if (!pushd("test/fish_expand_test")) return;
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"b/xx", fuzzy_comp, L"bax/xxx", L"baz/xxx", wnull, L"Wrong fuzzy matching 5");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-07-09 22:27:10 +00:00
|
|
|
// multiple slashes with fuzzy matching - #3185
|
2019-04-25 18:22:17 +00:00
|
|
|
expand_test(L"l///n", fuzzy_comp, L"lol///nub/", wnull, L"Wrong fuzzy matching 6");
|
2016-07-09 22:27:10 +00:00
|
|
|
|
2017-04-09 04:05:18 +00:00
|
|
|
popd();
|
2013-05-25 22:41:18 +00:00
|
|
|
}
|
|
|
|
|
2020-12-03 20:04:17 +00:00
|
|
|
static void test_expand_overflow() {
|
|
|
|
say(L"Testing overflowing expansions");
|
|
|
|
// Ensure that we have sane limits on number of expansions - see #7497.
|
|
|
|
|
|
|
|
// Make a list of 64 elements, then expand it cartesian-style 64 times.
|
|
|
|
// This is far too large to expand.
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> vals;
|
2020-12-03 20:04:17 +00:00
|
|
|
wcstring expansion;
|
|
|
|
for (int i = 1; i <= 64; i++) {
|
|
|
|
vals.push_back(to_string(i));
|
|
|
|
expansion.append(L"$bigvar");
|
|
|
|
}
|
|
|
|
|
|
|
|
auto parser = parser_t::principal_parser().shared();
|
|
|
|
parser->vars().push(true);
|
|
|
|
int set = parser->vars().set(L"bigvar", ENV_LOCAL, std::move(vals));
|
|
|
|
do_test(set == ENV_OK);
|
|
|
|
|
2023-02-05 08:35:06 +00:00
|
|
|
auto errors = new_parse_error_list();
|
2020-12-03 20:04:17 +00:00
|
|
|
operation_context_t ctx{parser, parser->vars(), no_cancel};
|
|
|
|
|
|
|
|
// We accept only 1024 completions.
|
|
|
|
completion_receiver_t output{1024};
|
|
|
|
|
2023-02-05 08:35:06 +00:00
|
|
|
auto res = expand_string(expansion, &output, expand_flags_t{}, ctx, &*errors);
|
|
|
|
do_test(!errors->empty());
|
2020-12-03 20:04:17 +00:00
|
|
|
do_test(res == expand_result_t::error);
|
|
|
|
|
|
|
|
parser->vars().pop();
|
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_abbreviations() {
|
2013-07-17 07:38:04 +00:00
|
|
|
say(L"Testing abbreviations");
|
2022-04-01 19:05:27 +00:00
|
|
|
{
|
2022-07-15 07:03:45 +00:00
|
|
|
auto abbrs = abbrs_get_set();
|
2023-02-24 16:00:05 +00:00
|
|
|
abbrs->add(L"gc", L"gc", L"git checkout", abbrs_position_t::command, false);
|
|
|
|
abbrs->add(L"foo", L"foo", L"bar", abbrs_position_t::command, false);
|
|
|
|
abbrs->add(L"gx", L"gx", L"git checkout", abbrs_position_t::command, false);
|
|
|
|
abbrs->add(L"yin", L"yin", L"yang", abbrs_position_t::anywhere, false);
|
2017-05-22 05:22:55 +00:00
|
|
|
}
|
2013-07-17 07:38:04 +00:00
|
|
|
|
2022-08-06 23:57:26 +00:00
|
|
|
// Helper to expand an abbreviation, enforcing we have no more than one result.
|
|
|
|
auto abbr_expand_1 = [](const wcstring &token, abbrs_position_t pos) -> maybe_t<wcstring> {
|
2022-12-10 20:45:21 +00:00
|
|
|
auto result = abbrs_match(token, pos);
|
2022-08-06 23:57:26 +00:00
|
|
|
if (result.size() > 1) {
|
|
|
|
err(L"abbreviation expansion for %ls returned more than 1 result", token.c_str());
|
|
|
|
}
|
|
|
|
if (result.empty()) {
|
|
|
|
return none();
|
|
|
|
}
|
2023-02-24 16:00:05 +00:00
|
|
|
return *result.front().replacement;
|
2022-08-06 23:57:26 +00:00
|
|
|
};
|
|
|
|
|
2022-04-01 19:05:27 +00:00
|
|
|
auto cmd = abbrs_position_t::command;
|
2022-08-06 23:57:26 +00:00
|
|
|
if (abbr_expand_1(L"", cmd)) err(L"Unexpected success with empty abbreviation");
|
|
|
|
if (abbr_expand_1(L"nothing", cmd)) err(L"Unexpected success with missing abbreviation");
|
2013-07-17 07:38:04 +00:00
|
|
|
|
2022-08-06 23:57:26 +00:00
|
|
|
auto mresult = abbr_expand_1(L"gc", cmd);
|
2018-09-24 05:47:38 +00:00
|
|
|
if (!mresult) err(L"Unexpected failure with gc abbreviation");
|
|
|
|
if (*mresult != L"git checkout") err(L"Wrong abbreviation result for gc");
|
2013-07-17 07:38:04 +00:00
|
|
|
|
2022-08-06 23:57:26 +00:00
|
|
|
mresult = abbr_expand_1(L"foo", cmd);
|
2018-09-24 05:47:38 +00:00
|
|
|
if (!mresult) err(L"Unexpected failure with foo abbreviation");
|
|
|
|
if (*mresult != L"bar") err(L"Wrong abbreviation result for foo");
|
2013-07-17 07:38:04 +00:00
|
|
|
|
2019-08-06 23:09:23 +00:00
|
|
|
maybe_t<wcstring> result;
|
2022-04-01 19:05:27 +00:00
|
|
|
auto expand_abbreviation_in_command = [](const wcstring &cmdline,
|
2022-12-17 16:05:22 +00:00
|
|
|
maybe_t<size_t> cursor_pos = {}) -> maybe_t<wcstring> {
|
|
|
|
if (auto replacement = reader_expand_abbreviation_at_cursor(
|
|
|
|
cmdline, cursor_pos.value_or(cmdline.size()), parser_t::principal_parser())) {
|
2020-02-04 11:47:44 +00:00
|
|
|
wcstring cmdline_expanded = cmdline;
|
2022-09-14 19:26:33 +00:00
|
|
|
std::vector<highlight_spec_t> colors{cmdline_expanded.size()};
|
2023-02-24 16:00:05 +00:00
|
|
|
apply_edit(&cmdline_expanded, &colors, edit_t{replacement->range, *replacement->text});
|
2020-02-04 11:47:44 +00:00
|
|
|
return cmdline_expanded;
|
|
|
|
}
|
|
|
|
return none_t();
|
|
|
|
};
|
2022-04-01 19:05:27 +00:00
|
|
|
result = expand_abbreviation_in_command(L"just a command", 3);
|
2019-08-06 23:09:23 +00:00
|
|
|
if (result) err(L"Command wrongly expanded on line %ld", (long)__LINE__);
|
2022-04-01 19:05:27 +00:00
|
|
|
result = expand_abbreviation_in_command(L"gc somebranch", 0);
|
2019-08-06 23:09:23 +00:00
|
|
|
if (!result) err(L"Command not expanded on line %ld", (long)__LINE__);
|
|
|
|
|
2022-04-01 19:05:27 +00:00
|
|
|
result = expand_abbreviation_in_command(L"gc somebranch", const_strlen(L"gc"));
|
2019-08-06 23:09:23 +00:00
|
|
|
if (!result) err(L"gc not expanded");
|
2016-05-01 01:37:19 +00:00
|
|
|
if (result != L"git checkout somebranch")
|
2019-08-06 23:09:23 +00:00
|
|
|
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Space separation.
|
2022-04-01 19:05:27 +00:00
|
|
|
result = expand_abbreviation_in_command(L"gx somebranch", const_strlen(L"gc"));
|
2019-08-06 23:09:23 +00:00
|
|
|
if (!result) err(L"gx not expanded");
|
2016-05-01 01:37:19 +00:00
|
|
|
if (result != L"git checkout somebranch")
|
2019-08-06 23:09:23 +00:00
|
|
|
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
|
2016-05-01 01:37:19 +00:00
|
|
|
|
2022-04-01 19:05:27 +00:00
|
|
|
result =
|
|
|
|
expand_abbreviation_in_command(L"echo hi ; gc somebranch", const_strlen(L"echo hi ; g"));
|
2019-08-06 23:09:23 +00:00
|
|
|
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (result != L"echo hi ; git checkout somebranch")
|
|
|
|
err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
|
|
|
|
|
2020-02-04 11:47:44 +00:00
|
|
|
result = expand_abbreviation_in_command(L"echo (echo (echo (echo (gc ",
|
2022-04-01 19:05:27 +00:00
|
|
|
const_strlen(L"echo (echo (echo (echo (gc"));
|
2019-08-06 23:09:23 +00:00
|
|
|
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (result != L"echo (echo (echo (echo (git checkout ")
|
2019-08-06 23:09:23 +00:00
|
|
|
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
|
2016-05-01 01:37:19 +00:00
|
|
|
|
|
|
|
// If commands should be expanded.
|
2022-12-17 16:05:22 +00:00
|
|
|
result = expand_abbreviation_in_command(L"if gc");
|
2019-08-06 23:09:23 +00:00
|
|
|
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (result != L"if git checkout")
|
2019-08-06 23:09:23 +00:00
|
|
|
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
|
2013-07-17 09:55:15 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Others should not be.
|
2022-12-17 16:05:22 +00:00
|
|
|
result = expand_abbreviation_in_command(L"of gc");
|
2019-08-06 23:09:23 +00:00
|
|
|
if (result) err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
|
2013-07-17 07:38:04 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Others should not be.
|
2022-12-17 16:05:22 +00:00
|
|
|
result = expand_abbreviation_in_command(L"command gc");
|
2019-08-06 23:09:23 +00:00
|
|
|
if (result) err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
|
2013-10-09 09:03:50 +00:00
|
|
|
|
2022-04-01 19:05:27 +00:00
|
|
|
// yin/yang expands everywhere.
|
2022-12-17 16:05:22 +00:00
|
|
|
result = expand_abbreviation_in_command(L"command yin");
|
2022-04-01 19:05:27 +00:00
|
|
|
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
|
|
|
|
if (result != L"command yang") {
|
|
|
|
err(L"command yin incorrectly expanded on line %ld to '%ls'", (long)__LINE__,
|
|
|
|
result->c_str());
|
|
|
|
}
|
2013-07-17 07:38:04 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_pager_navigation() {
|
2014-01-20 00:41:26 +00:00
|
|
|
say(L"Testing pager navigation");
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Generate 19 strings of width 10. There's 2 spaces between completions, and our term size is
|
|
|
|
// 80; these can therefore fit into 6 columns (6 * 12 - 2 = 70) or 5 columns (58) but not 7
|
|
|
|
// columns (7 * 12 - 2 = 82).
|
|
|
|
//
|
|
|
|
// You can simulate this test by creating 19 files named "file00.txt" through "file_18.txt".
|
2014-01-20 00:41:26 +00:00
|
|
|
completion_list_t completions;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < 19; i++) {
|
2015-07-28 01:45:47 +00:00
|
|
|
append_completion(&completions, L"abcdefghij");
|
2014-01-20 00:41:26 +00:00
|
|
|
}
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2014-01-20 00:41:26 +00:00
|
|
|
pager_t pager;
|
2020-12-02 18:11:36 +00:00
|
|
|
pager.set_completions(completions);
|
2023-03-19 22:50:33 +00:00
|
|
|
pager.set_term_size(termsize_default());
|
2014-01-20 00:41:26 +00:00
|
|
|
page_rendering_t render = pager.render();
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
if (render.term_width != 80) err(L"Wrong term width");
|
|
|
|
if (render.term_height != 24) err(L"Wrong term height");
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2014-01-20 00:41:26 +00:00
|
|
|
size_t rows = 4, cols = 5;
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// We have 19 completions. We can fit into 6 columns with 4 rows or 5 columns with 4 rows; the
|
|
|
|
// second one is better and so is what we ought to have picked.
|
|
|
|
if (render.rows != rows) err(L"Wrong row count");
|
|
|
|
if (render.cols != cols) err(L"Wrong column count");
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Initially expect to have no completion index.
|
|
|
|
if (render.selected_completion_idx != (size_t)(-1)) {
|
2014-01-20 00:41:26 +00:00
|
|
|
err(L"Wrong initial selection");
|
|
|
|
}
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Here are navigation directions and where we expect the selection to be.
|
|
|
|
const struct {
|
2019-04-28 21:06:03 +00:00
|
|
|
selection_motion_t dir;
|
2014-01-20 00:41:26 +00:00
|
|
|
size_t sel;
|
2016-05-01 01:37:19 +00:00
|
|
|
} cmds[] = {
|
|
|
|
// Tab completion to get into the list.
|
2019-04-28 21:06:03 +00:00
|
|
|
{selection_motion_t::next, 0},
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2018-01-24 11:08:52 +00:00
|
|
|
// Westward motion in upper left goes to the last filled column in the last row.
|
2019-04-28 21:06:03 +00:00
|
|
|
{selection_motion_t::west, 15},
|
2018-01-24 11:08:52 +00:00
|
|
|
// East goes back.
|
2019-04-28 21:06:03 +00:00
|
|
|
{selection_motion_t::east, 0},
|
2022-07-24 14:49:12 +00:00
|
|
|
|
|
|
|
{selection_motion_t::west, 15},
|
|
|
|
{selection_motion_t::west, 11},
|
|
|
|
{selection_motion_t::east, 15},
|
|
|
|
{selection_motion_t::east, 0},
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// "Next" motion goes down the column.
|
2019-04-28 21:06:03 +00:00
|
|
|
{selection_motion_t::next, 1},
|
|
|
|
{selection_motion_t::next, 2},
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2019-04-28 21:06:03 +00:00
|
|
|
{selection_motion_t::west, 17},
|
|
|
|
{selection_motion_t::east, 2},
|
|
|
|
{selection_motion_t::east, 6},
|
|
|
|
{selection_motion_t::east, 10},
|
|
|
|
{selection_motion_t::east, 14},
|
|
|
|
{selection_motion_t::east, 18},
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2019-04-28 21:06:03 +00:00
|
|
|
{selection_motion_t::west, 14},
|
|
|
|
{selection_motion_t::east, 18},
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2018-01-24 11:08:52 +00:00
|
|
|
// Eastward motion wraps back to the upper left, westward goes to the prior column.
|
2019-04-28 21:06:03 +00:00
|
|
|
{selection_motion_t::east, 3},
|
|
|
|
{selection_motion_t::east, 7},
|
|
|
|
{selection_motion_t::east, 11},
|
|
|
|
{selection_motion_t::east, 15},
|
2015-03-02 21:08:29 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Pages.
|
2019-04-28 21:06:03 +00:00
|
|
|
{selection_motion_t::page_north, 12},
|
|
|
|
{selection_motion_t::page_south, 15},
|
|
|
|
{selection_motion_t::page_north, 12},
|
|
|
|
{selection_motion_t::east, 16},
|
|
|
|
{selection_motion_t::page_south, 18},
|
|
|
|
{selection_motion_t::east, 3},
|
|
|
|
{selection_motion_t::north, 2},
|
|
|
|
{selection_motion_t::page_north, 0},
|
|
|
|
{selection_motion_t::page_south, 3},
|
2015-03-02 21:08:29 +00:00
|
|
|
|
2014-01-20 00:41:26 +00:00
|
|
|
};
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < sizeof cmds / sizeof *cmds; i++) {
|
2014-01-20 00:41:26 +00:00
|
|
|
pager.select_next_completion_in_direction(cmds[i].dir, render);
|
|
|
|
pager.update_rendering(&render);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (cmds[i].sel != render.selected_completion_idx) {
|
|
|
|
err(L"For command %lu, expected selection %lu, but found instead %lu\n", i, cmds[i].sel,
|
|
|
|
render.selected_completion_idx);
|
2014-01-20 00:41:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-03 21:12:44 +00:00
|
|
|
struct pager_layout_testcase_t {
|
2020-06-08 01:59:15 +00:00
|
|
|
int width;
|
2016-12-03 21:12:44 +00:00
|
|
|
const wchar_t *expected;
|
|
|
|
|
2017-02-11 02:47:02 +00:00
|
|
|
// Run ourselves as a test case.
|
|
|
|
// Set our data on the pager, and then check the rendering.
|
|
|
|
// We should have one line, and it should have our expected text.
|
2016-12-03 21:12:44 +00:00
|
|
|
void run(pager_t &pager) const {
|
2020-06-08 01:59:15 +00:00
|
|
|
pager.set_term_size(termsize_t{this->width, 24});
|
2016-12-03 21:12:44 +00:00
|
|
|
page_rendering_t rendering = pager.render();
|
|
|
|
const screen_data_t &sd = rendering.screen_data;
|
|
|
|
do_test(sd.line_count() == 1);
|
|
|
|
if (sd.line_count() > 0) {
|
|
|
|
wcstring expected = this->expected;
|
|
|
|
|
|
|
|
// hack: handle the case where ellipsis is not L'\x2026'
|
2019-04-28 22:00:36 +00:00
|
|
|
wchar_t ellipsis_char = get_ellipsis_char();
|
2016-12-03 21:12:44 +00:00
|
|
|
if (ellipsis_char != L'\x2026') {
|
|
|
|
std::replace(expected.begin(), expected.end(), L'\x2026', ellipsis_char);
|
|
|
|
}
|
|
|
|
|
2020-06-07 20:53:41 +00:00
|
|
|
wcstring text;
|
|
|
|
for (const auto &p : sd.line(0).text) {
|
2020-07-05 01:23:50 +00:00
|
|
|
text.push_back(p.character);
|
2020-06-07 20:53:41 +00:00
|
|
|
}
|
2016-12-03 21:12:44 +00:00
|
|
|
if (text != expected) {
|
2020-12-25 08:25:08 +00:00
|
|
|
std::fwprintf(stderr, L"width %d got %zu<%ls>, expected %zu<%ls>\n", this->width,
|
2019-05-05 10:09:25 +00:00
|
|
|
text.length(), text.c_str(), expected.length(), expected.c_str());
|
2017-02-16 04:09:26 +00:00
|
|
|
for (size_t i = 0; i < std::max(text.length(), expected.length()); i++) {
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, L"i %zu got <%lx> expected <%lx>\n", i,
|
2019-05-05 10:09:25 +00:00
|
|
|
i >= text.length() ? 0xffff : text[i],
|
|
|
|
i >= expected.length() ? 0xffff : expected[i]);
|
2017-02-16 04:09:26 +00:00
|
|
|
}
|
2016-12-03 21:12:44 +00:00
|
|
|
}
|
|
|
|
do_test(text == expected);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static void test_pager_layout() {
|
|
|
|
// These tests are woefully incomplete
|
|
|
|
// They only test the truncation logic for a single completion
|
|
|
|
say(L"Testing pager layout");
|
|
|
|
pager_t pager;
|
|
|
|
|
|
|
|
// These test cases have equal completions and descriptions
|
|
|
|
const completion_t c1(L"abcdefghij", L"1234567890");
|
2020-12-02 18:11:36 +00:00
|
|
|
pager.set_completions(completion_list_t(1, c1));
|
2016-12-03 21:12:44 +00:00
|
|
|
const pager_layout_testcase_t testcases1[] = {
|
2016-12-04 04:12:53 +00:00
|
|
|
{26, L"abcdefghij (1234567890)"}, {25, L"abcdefghij (1234567890)"},
|
|
|
|
{24, L"abcdefghij (1234567890)"}, {23, L"abcdefghij (12345678…)"},
|
|
|
|
{22, L"abcdefghij (1234567…)"}, {21, L"abcdefghij (123456…)"},
|
|
|
|
{20, L"abcdefghij (12345…)"}, {19, L"abcdefghij (1234…)"},
|
|
|
|
{18, L"abcdefgh… (1234…)"}, {17, L"abcdefg… (1234…)"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{16, L"abcdefg… (123…)"}, {0, nullptr} // sentinel terminator
|
2016-12-03 21:12:44 +00:00
|
|
|
};
|
2021-08-17 22:57:52 +00:00
|
|
|
for (size_t i = 0; testcases1[i].expected != nullptr; i++) {
|
2016-12-03 21:12:44 +00:00
|
|
|
testcases1[i].run(pager);
|
|
|
|
}
|
|
|
|
|
|
|
|
// These test cases have heavyweight completions
|
|
|
|
const completion_t c2(L"abcdefghijklmnopqrs", L"1");
|
2020-12-02 18:11:36 +00:00
|
|
|
pager.set_completions(completion_list_t(1, c2));
|
2016-12-03 21:12:44 +00:00
|
|
|
const pager_layout_testcase_t testcases2[] = {
|
2016-12-04 04:12:53 +00:00
|
|
|
{26, L"abcdefghijklmnopqrs (1)"}, {25, L"abcdefghijklmnopqrs (1)"},
|
|
|
|
{24, L"abcdefghijklmnopqrs (1)"}, {23, L"abcdefghijklmnopq… (1)"},
|
|
|
|
{22, L"abcdefghijklmnop… (1)"}, {21, L"abcdefghijklmno… (1)"},
|
|
|
|
{20, L"abcdefghijklmn… (1)"}, {19, L"abcdefghijklm… (1)"},
|
|
|
|
{18, L"abcdefghijkl… (1)"}, {17, L"abcdefghijk… (1)"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{16, L"abcdefghij… (1)"}, {0, nullptr} // sentinel terminator
|
2016-12-03 21:12:44 +00:00
|
|
|
};
|
2021-08-17 22:57:52 +00:00
|
|
|
for (size_t i = 0; testcases2[i].expected != nullptr; i++) {
|
2016-12-03 21:12:44 +00:00
|
|
|
testcases2[i].run(pager);
|
|
|
|
}
|
|
|
|
|
|
|
|
// These test cases have no descriptions
|
|
|
|
const completion_t c3(L"abcdefghijklmnopqrst", L"");
|
2020-12-02 18:11:36 +00:00
|
|
|
pager.set_completions(completion_list_t(1, c3));
|
2016-12-03 21:12:44 +00:00
|
|
|
const pager_layout_testcase_t testcases3[] = {
|
2016-12-04 04:12:53 +00:00
|
|
|
{26, L"abcdefghijklmnopqrst"}, {25, L"abcdefghijklmnopqrst"},
|
|
|
|
{24, L"abcdefghijklmnopqrst"}, {23, L"abcdefghijklmnopqrst"},
|
|
|
|
{22, L"abcdefghijklmnopqrst"}, {21, L"abcdefghijklmnopqrst"},
|
|
|
|
{20, L"abcdefghijklmnopqrst"}, {19, L"abcdefghijklmnopqr…"},
|
|
|
|
{18, L"abcdefghijklmnopq…"}, {17, L"abcdefghijklmnop…"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{16, L"abcdefghijklmno…"}, {0, nullptr} // sentinel terminator
|
2016-12-03 21:12:44 +00:00
|
|
|
};
|
2021-08-17 22:57:52 +00:00
|
|
|
for (size_t i = 0; testcases3[i].expected != nullptr; i++) {
|
2016-12-03 21:12:44 +00:00
|
|
|
testcases3[i].run(pager);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
enum word_motion_t { word_motion_left, word_motion_right };
|
|
|
|
static void test_1_word_motion(word_motion_t motion, move_word_style_t style,
|
|
|
|
const wcstring &test) {
|
2012-12-21 01:37:09 +00:00
|
|
|
wcstring command;
|
|
|
|
std::set<size_t> stops;
|
2012-12-22 20:21:31 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Carets represent stops and should be cut out of the command.
|
2020-06-30 22:29:06 +00:00
|
|
|
for (wchar_t wc : test) {
|
2016-05-01 01:37:19 +00:00
|
|
|
if (wc == L'^') {
|
2012-12-21 01:37:09 +00:00
|
|
|
stops.insert(command.size());
|
2016-05-01 01:37:19 +00:00
|
|
|
} else {
|
2012-12-21 01:37:09 +00:00
|
|
|
command.push_back(wc);
|
|
|
|
}
|
|
|
|
}
|
2012-12-22 20:21:31 +00:00
|
|
|
|
2012-12-21 01:37:09 +00:00
|
|
|
size_t idx, end;
|
2016-05-01 01:37:19 +00:00
|
|
|
if (motion == word_motion_left) {
|
2020-09-27 13:34:34 +00:00
|
|
|
idx = *std::max_element(stops.begin(), stops.end());
|
2012-12-21 01:37:09 +00:00
|
|
|
end = 0;
|
2016-05-01 01:37:19 +00:00
|
|
|
} else {
|
2020-09-27 13:34:34 +00:00
|
|
|
idx = *std::min_element(stops.begin(), stops.end());
|
2012-12-21 01:37:09 +00:00
|
|
|
end = command.size();
|
|
|
|
}
|
2020-09-27 13:34:34 +00:00
|
|
|
stops.erase(idx);
|
2012-12-22 20:21:31 +00:00
|
|
|
|
2023-02-05 13:06:11 +00:00
|
|
|
auto sm = new_move_word_state_machine(style);
|
2016-05-01 01:37:19 +00:00
|
|
|
while (idx != end) {
|
2012-12-21 01:37:09 +00:00
|
|
|
size_t char_idx = (motion == word_motion_left ? idx - 1 : idx);
|
|
|
|
wchar_t wc = command.at(char_idx);
|
2023-02-05 13:06:11 +00:00
|
|
|
bool will_stop = !sm->consume_char(wc);
|
2019-03-12 21:06:01 +00:00
|
|
|
// std::fwprintf(stdout, L"idx %lu, looking at %lu (%c): %d\n", idx, char_idx, (char)wc,
|
2017-01-14 04:34:15 +00:00
|
|
|
// will_stop);
|
2012-12-21 01:37:09 +00:00
|
|
|
bool expected_stop = (stops.count(idx) > 0);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (will_stop != expected_stop) {
|
2012-12-21 01:37:09 +00:00
|
|
|
wcstring tmp = command;
|
|
|
|
tmp.insert(idx, L"^");
|
|
|
|
const char *dir = (motion == word_motion_left ? "left" : "right");
|
2016-05-01 01:37:19 +00:00
|
|
|
if (will_stop) {
|
|
|
|
err(L"Word motion: moving %s, unexpected stop at idx %lu: '%ls'", dir, idx,
|
|
|
|
tmp.c_str());
|
|
|
|
} else if (!will_stop && expected_stop) {
|
|
|
|
err(L"Word motion: moving %s, should have stopped at idx %lu: '%ls'", dir, idx,
|
|
|
|
tmp.c_str());
|
2012-12-21 01:37:09 +00:00
|
|
|
}
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
// We don't expect to stop here next time.
|
|
|
|
if (expected_stop) {
|
2012-12-21 01:37:09 +00:00
|
|
|
stops.erase(idx);
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
if (will_stop) {
|
2023-02-05 13:06:11 +00:00
|
|
|
sm->reset();
|
2016-05-01 01:37:19 +00:00
|
|
|
} else {
|
2012-12-21 01:37:09 +00:00
|
|
|
idx += (motion == word_motion_left ? -1 : 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Test word motion (forward-word, etc.). Carets represent cursor stops.
|
|
|
|
static void test_word_motion() {
|
2012-12-21 01:37:09 +00:00
|
|
|
say(L"Testing word motion");
|
2023-02-05 13:06:11 +00:00
|
|
|
test_1_word_motion(word_motion_left, move_word_style_t::move_word_style_punctuation,
|
|
|
|
L"^echo ^hello_^world.^txt^");
|
|
|
|
test_1_word_motion(word_motion_right, move_word_style_t::move_word_style_punctuation,
|
2020-09-27 15:53:18 +00:00
|
|
|
L"^echo^ hello^_world^.txt^");
|
2012-12-22 20:21:31 +00:00
|
|
|
|
2023-02-05 13:06:11 +00:00
|
|
|
test_1_word_motion(word_motion_left, move_word_style_t::move_word_style_punctuation,
|
2020-09-27 13:34:34 +00:00
|
|
|
L"echo ^foo_^foo_^foo/^/^/^/^/^ ^");
|
2023-02-05 13:06:11 +00:00
|
|
|
test_1_word_motion(word_motion_right, move_word_style_t::move_word_style_punctuation,
|
2020-09-27 13:34:34 +00:00
|
|
|
L"^echo^ foo^_foo^_foo^/^/^/^/^/ ^");
|
2012-12-22 20:21:31 +00:00
|
|
|
|
2023-02-05 13:06:11 +00:00
|
|
|
test_1_word_motion(word_motion_left, move_word_style_t::move_word_style_path_components,
|
|
|
|
L"^/^foo/^bar/^baz/^");
|
|
|
|
test_1_word_motion(word_motion_left, move_word_style_t::move_word_style_path_components,
|
|
|
|
L"^echo ^--foo ^--bar^");
|
|
|
|
test_1_word_motion(word_motion_left, move_word_style_t::move_word_style_path_components,
|
2021-03-29 20:58:50 +00:00
|
|
|
L"^echo ^hi ^> ^/^dev/^null^");
|
2012-12-22 20:21:31 +00:00
|
|
|
|
2023-02-05 13:06:11 +00:00
|
|
|
test_1_word_motion(word_motion_left, move_word_style_t::move_word_style_path_components,
|
2021-03-29 20:58:50 +00:00
|
|
|
L"^echo ^/^foo/^bar{^aaa,^bbb,^ccc}^bak/^");
|
2023-02-05 13:06:11 +00:00
|
|
|
test_1_word_motion(word_motion_left, move_word_style_t::move_word_style_path_components,
|
|
|
|
L"^echo ^bak ^///^");
|
|
|
|
test_1_word_motion(word_motion_left, move_word_style_t::move_word_style_path_components,
|
|
|
|
L"^aaa ^@ ^@^aaa^");
|
|
|
|
test_1_word_motion(word_motion_left, move_word_style_t::move_word_style_path_components,
|
|
|
|
L"^aaa ^a ^@^aaa^");
|
|
|
|
test_1_word_motion(word_motion_left, move_word_style_t::move_word_style_path_components,
|
|
|
|
L"^aaa ^@@@ ^@@^aa^");
|
|
|
|
test_1_word_motion(word_motion_left, move_word_style_t::move_word_style_path_components,
|
|
|
|
L"^aa^@@ ^aa@@^a^");
|
|
|
|
|
|
|
|
test_1_word_motion(word_motion_right, move_word_style_t::move_word_style_punctuation,
|
|
|
|
L"^a^ bcd^");
|
|
|
|
test_1_word_motion(word_motion_right, move_word_style_t::move_word_style_punctuation,
|
|
|
|
L"a^b^ cde^");
|
|
|
|
test_1_word_motion(word_motion_right, move_word_style_t::move_word_style_punctuation,
|
|
|
|
L"^ab^ cde^");
|
|
|
|
test_1_word_motion(word_motion_right, move_word_style_t::move_word_style_punctuation,
|
|
|
|
L"^ab^&cd^ ^& ^e^ f^&");
|
|
|
|
|
|
|
|
test_1_word_motion(word_motion_right, move_word_style_t::move_word_style_whitespace,
|
|
|
|
L"^^a-b-c^ d-e-f");
|
|
|
|
test_1_word_motion(word_motion_right, move_word_style_t::move_word_style_whitespace,
|
|
|
|
L"^a-b-c^\n d-e-f^ ");
|
|
|
|
test_1_word_motion(word_motion_right, move_word_style_t::move_word_style_whitespace,
|
|
|
|
L"^a-b-c^\n\nd-e-f^ ");
|
2012-12-21 01:37:09 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Test is_potential_path.
|
|
|
|
static void test_is_potential_path() {
|
2012-05-14 03:19:02 +00:00
|
|
|
say(L"Testing is_potential_path");
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Directories
|
2017-04-09 04:05:18 +00:00
|
|
|
if (system("mkdir -p test/is_potential_path_test/alpha/")) err(L"mkdir failed");
|
|
|
|
if (system("mkdir -p test/is_potential_path_test/beta/")) err(L"mkdir failed");
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Files
|
2017-04-09 04:05:18 +00:00
|
|
|
if (system("touch test/is_potential_path_test/aardvark")) err(L"touch failed");
|
|
|
|
if (system("touch test/is_potential_path_test/gamma")) err(L"touch failed");
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2017-04-09 04:05:18 +00:00
|
|
|
const wcstring wd = L"test/is_potential_path_test/";
|
2023-04-18 22:19:10 +00:00
|
|
|
const std::vector<wcstring> wds({L".", wd});
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2020-03-03 03:47:21 +00:00
|
|
|
operation_context_t ctx{env_stack_t::principal()};
|
2022-10-26 10:29:36 +00:00
|
|
|
do_test(is_potential_path(L"al", true, wds, ctx, PATH_REQUIRE_DIR));
|
|
|
|
do_test(is_potential_path(L"alpha/", true, wds, ctx, PATH_REQUIRE_DIR));
|
|
|
|
do_test(is_potential_path(L"aard", true, wds, ctx, 0));
|
|
|
|
do_test(!is_potential_path(L"aard", false, wds, ctx, 0));
|
2022-12-03 21:34:52 +00:00
|
|
|
do_test(!is_potential_path(L"alp/", true, wds, ctx, PATH_REQUIRE_DIR | PATH_FOR_CD));
|
2022-10-26 10:29:36 +00:00
|
|
|
|
|
|
|
do_test(!is_potential_path(L"balpha/", true, wds, ctx, PATH_REQUIRE_DIR));
|
|
|
|
do_test(!is_potential_path(L"aard", true, wds, ctx, PATH_REQUIRE_DIR));
|
|
|
|
do_test(!is_potential_path(L"aarde", true, wds, ctx, PATH_REQUIRE_DIR));
|
|
|
|
do_test(!is_potential_path(L"aarde", true, wds, ctx, 0));
|
|
|
|
|
|
|
|
do_test(is_potential_path(L"test/is_potential_path_test/aardvark", true, wds, ctx, 0));
|
|
|
|
do_test(is_potential_path(L"test/is_potential_path_test/al", true, wds, ctx, PATH_REQUIRE_DIR));
|
|
|
|
do_test(is_potential_path(L"test/is_potential_path_test/aardv", true, wds, ctx, 0));
|
|
|
|
|
|
|
|
do_test(!is_potential_path(L"test/is_potential_path_test/aardvark", true, wds, ctx,
|
|
|
|
PATH_REQUIRE_DIR));
|
|
|
|
do_test(!is_potential_path(L"test/is_potential_path_test/al/", true, wds, ctx, 0));
|
|
|
|
do_test(!is_potential_path(L"test/is_potential_path_test/ar", true, wds, ctx, 0));
|
|
|
|
|
|
|
|
do_test(is_potential_path(L"/usr", true, wds, ctx, PATH_REQUIRE_DIR));
|
2012-05-14 03:19:02 +00:00
|
|
|
}
|
|
|
|
|
2018-11-04 23:53:31 +00:00
|
|
|
static void test_wcstod() {
|
|
|
|
say(L"Testing fish_wcstod");
|
|
|
|
auto tod_test = [](const wchar_t *a, const char *b) {
|
|
|
|
char *narrow_end = nullptr;
|
|
|
|
wchar_t *wide_end = nullptr;
|
2021-12-27 06:06:49 +00:00
|
|
|
double val1 = fish_wcstod(a, &wide_end);
|
2018-11-04 23:53:31 +00:00
|
|
|
double val2 = strtod(b, &narrow_end);
|
2019-01-05 11:31:24 +00:00
|
|
|
do_test((std::isnan(val1) && std::isnan(val2)) || fabs(val1 - val2) <= __DBL_EPSILON__);
|
2018-11-04 23:53:31 +00:00
|
|
|
do_test(wide_end - a == narrow_end - b);
|
|
|
|
};
|
|
|
|
tod_test(L"", "");
|
|
|
|
tod_test(L"1.2", "1.2");
|
|
|
|
tod_test(L"1.5", "1.5");
|
|
|
|
tod_test(L"-1000", "-1000");
|
|
|
|
tod_test(L"0.12345", "0.12345");
|
|
|
|
tod_test(L"nope", "nope");
|
|
|
|
}
|
|
|
|
|
2019-01-28 21:26:22 +00:00
|
|
|
static void test_dup2s() {
|
|
|
|
using std::make_shared;
|
|
|
|
io_chain_t chain;
|
|
|
|
chain.push_back(make_shared<io_close_t>(17));
|
2019-12-19 22:14:23 +00:00
|
|
|
chain.push_back(make_shared<io_fd_t>(3, 19));
|
2023-02-04 10:21:42 +00:00
|
|
|
auto list = dup2_list_resolve_chain_shim(chain);
|
2019-12-29 22:49:05 +00:00
|
|
|
do_test(list.get_actions().size() == 2);
|
2019-01-28 21:26:22 +00:00
|
|
|
|
2019-12-29 22:49:05 +00:00
|
|
|
auto act1 = list.get_actions().at(0);
|
2019-01-28 21:26:22 +00:00
|
|
|
do_test(act1.src == 17);
|
|
|
|
do_test(act1.target == -1);
|
|
|
|
|
2019-12-29 22:49:05 +00:00
|
|
|
auto act2 = list.get_actions().at(1);
|
2019-01-28 21:26:22 +00:00
|
|
|
do_test(act2.src == 19);
|
|
|
|
do_test(act2.target == 3);
|
|
|
|
}
|
|
|
|
|
2019-02-02 20:52:51 +00:00
|
|
|
static void test_dup2s_fd_for_target_fd() {
|
|
|
|
using std::make_shared;
|
|
|
|
io_chain_t chain;
|
|
|
|
// note io_fd_t params are backwards from dup2.
|
|
|
|
chain.push_back(make_shared<io_close_t>(10));
|
2019-12-19 22:14:23 +00:00
|
|
|
chain.push_back(make_shared<io_fd_t>(9, 10));
|
|
|
|
chain.push_back(make_shared<io_fd_t>(5, 8));
|
|
|
|
chain.push_back(make_shared<io_fd_t>(1, 4));
|
|
|
|
chain.push_back(make_shared<io_fd_t>(3, 5));
|
2023-02-04 10:21:42 +00:00
|
|
|
auto list = dup2_list_resolve_chain_shim(chain);
|
2019-02-02 20:52:51 +00:00
|
|
|
|
2019-12-29 22:49:05 +00:00
|
|
|
do_test(list.fd_for_target_fd(3) == 8);
|
|
|
|
do_test(list.fd_for_target_fd(5) == 8);
|
|
|
|
do_test(list.fd_for_target_fd(8) == 8);
|
|
|
|
do_test(list.fd_for_target_fd(1) == 4);
|
|
|
|
do_test(list.fd_for_target_fd(4) == 4);
|
|
|
|
do_test(list.fd_for_target_fd(100) == 100);
|
|
|
|
do_test(list.fd_for_target_fd(0) == 0);
|
|
|
|
do_test(list.fd_for_target_fd(-1) == -1);
|
|
|
|
do_test(list.fd_for_target_fd(9) == -1);
|
|
|
|
do_test(list.fd_for_target_fd(10) == -1);
|
2019-02-02 20:52:51 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Testing colors.
|
|
|
|
static void test_colors() {
|
2012-02-13 02:05:59 +00:00
|
|
|
say(L"Testing colors");
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(rgb_color_t(L"#FF00A0").is_rgb());
|
|
|
|
do_test(rgb_color_t(L"FF00A0").is_rgb());
|
|
|
|
do_test(rgb_color_t(L"#F30").is_rgb());
|
|
|
|
do_test(rgb_color_t(L"F30").is_rgb());
|
|
|
|
do_test(rgb_color_t(L"f30").is_rgb());
|
|
|
|
do_test(rgb_color_t(L"#FF30a5").is_rgb());
|
|
|
|
do_test(rgb_color_t(L"3f30").is_none());
|
|
|
|
do_test(rgb_color_t(L"##f30").is_none());
|
|
|
|
do_test(rgb_color_t(L"magenta").is_named());
|
|
|
|
do_test(rgb_color_t(L"MaGeNTa").is_named());
|
|
|
|
do_test(rgb_color_t(L"mooganta").is_none());
|
2012-02-13 02:05:59 +00:00
|
|
|
}
|
2007-05-10 19:11:28 +00:00
|
|
|
|
2019-04-28 18:08:18 +00:00
|
|
|
// This class allows accessing private bits of autoload_t.
|
|
|
|
struct autoload_tester_t {
|
|
|
|
static void run(const wchar_t *fmt, ...) {
|
|
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
|
|
wcstring cmd = vformat_string(fmt, va);
|
|
|
|
va_end(va);
|
|
|
|
|
2023-04-01 11:50:30 +00:00
|
|
|
int status = system(wcs2zstring(cmd).c_str());
|
2019-04-28 18:08:18 +00:00
|
|
|
do_test(status == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void touch_file(const wcstring &path) {
|
|
|
|
int fd = wopen_cloexec(path, O_RDWR | O_CREAT, 0666);
|
|
|
|
do_test(fd >= 0);
|
|
|
|
write_loop(fd, "Hello", 5);
|
|
|
|
close(fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void run_test() {
|
|
|
|
char t1[] = "/tmp/fish_test_autoload.XXXXXX";
|
|
|
|
wcstring p1 = str2wcstring(mkdtemp(t1));
|
|
|
|
char t2[] = "/tmp/fish_test_autoload.XXXXXX";
|
|
|
|
wcstring p2 = str2wcstring(mkdtemp(t2));
|
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
const std::vector<wcstring> paths = {p1, p2};
|
2019-04-28 18:08:18 +00:00
|
|
|
|
|
|
|
autoload_t autoload(L"test_var");
|
|
|
|
do_test(!autoload.resolve_command(L"file1", paths));
|
|
|
|
do_test(!autoload.resolve_command(L"nothing", paths));
|
|
|
|
do_test(autoload.get_autoloaded_commands().empty());
|
|
|
|
|
|
|
|
run(L"touch %ls/file1.fish", p1.c_str());
|
|
|
|
run(L"touch %ls/file2.fish", p2.c_str());
|
|
|
|
autoload.invalidate_cache();
|
|
|
|
|
|
|
|
do_test(!autoload.autoload_in_progress(L"file1"));
|
|
|
|
do_test(autoload.resolve_command(L"file1", paths));
|
|
|
|
do_test(!autoload.resolve_command(L"file1", paths));
|
|
|
|
do_test(autoload.autoload_in_progress(L"file1"));
|
2023-04-18 22:19:10 +00:00
|
|
|
do_test(autoload.get_autoloaded_commands() == std::vector<wcstring>{L"file1"});
|
2019-04-28 18:08:18 +00:00
|
|
|
autoload.mark_autoload_finished(L"file1");
|
|
|
|
do_test(!autoload.autoload_in_progress(L"file1"));
|
2023-04-18 22:19:10 +00:00
|
|
|
do_test(autoload.get_autoloaded_commands() == std::vector<wcstring>{L"file1"});
|
2019-04-28 18:08:18 +00:00
|
|
|
|
|
|
|
do_test(!autoload.resolve_command(L"file1", paths));
|
|
|
|
do_test(!autoload.resolve_command(L"nothing", paths));
|
|
|
|
do_test(autoload.resolve_command(L"file2", paths));
|
|
|
|
do_test(!autoload.resolve_command(L"file2", paths));
|
|
|
|
autoload.mark_autoload_finished(L"file2");
|
|
|
|
do_test(!autoload.resolve_command(L"file2", paths));
|
2023-04-18 22:19:10 +00:00
|
|
|
do_test((autoload.get_autoloaded_commands() == std::vector<wcstring>{L"file1", L"file2"}));
|
2019-04-28 18:08:18 +00:00
|
|
|
|
|
|
|
autoload.clear();
|
|
|
|
do_test(autoload.resolve_command(L"file1", paths));
|
|
|
|
autoload.mark_autoload_finished(L"file1");
|
|
|
|
do_test(!autoload.resolve_command(L"file1", paths));
|
|
|
|
do_test(!autoload.resolve_command(L"nothing", paths));
|
|
|
|
do_test(autoload.resolve_command(L"file2", paths));
|
|
|
|
do_test(!autoload.resolve_command(L"file2", paths));
|
|
|
|
autoload.mark_autoload_finished(L"file2");
|
|
|
|
|
|
|
|
do_test(!autoload.resolve_command(L"file1", paths));
|
|
|
|
touch_file(format_string(L"%ls/file1.fish", p1.c_str()));
|
|
|
|
autoload.invalidate_cache();
|
|
|
|
do_test(autoload.resolve_command(L"file1", paths));
|
|
|
|
autoload.mark_autoload_finished(L"file1");
|
|
|
|
|
|
|
|
run(L"rm -Rf %ls", p1.c_str());
|
|
|
|
run(L"rm -Rf %ls", p2.c_str());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static void test_autoload() {
|
|
|
|
say(L"Testing autoload");
|
|
|
|
autoload_tester_t::run_test();
|
|
|
|
}
|
|
|
|
|
2021-10-21 20:34:46 +00:00
|
|
|
// Construct function properties for testing.
|
|
|
|
static std::shared_ptr<function_properties_t> make_test_func_props() {
|
|
|
|
auto ret = std::make_shared<function_properties_t>();
|
|
|
|
ret->parsed_source = parse_source(L"function stuff; end", parse_flag_none, nullptr);
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
assert(ret->parsed_source->has_value() && "Failed to parse");
|
|
|
|
for (auto ast_traversal = new_ast_traversal(*ret->parsed_source->ast().top());;) {
|
|
|
|
auto node = ast_traversal->next();
|
|
|
|
if (!node->has_value()) break;
|
|
|
|
if (const auto *s = node->try_as_block_statement()) {
|
2021-10-21 20:34:46 +00:00
|
|
|
ret->func_node = s;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert(ret->func_node && "Unable to find block statement");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-11-27 20:46:15 +00:00
|
|
|
static void test_wildcards() {
|
|
|
|
say(L"Testing wildcards");
|
|
|
|
do_test(!wildcard_has(L""));
|
|
|
|
do_test(wildcard_has(L"*"));
|
|
|
|
do_test(!wildcard_has(L"\\*"));
|
|
|
|
do_test(!wildcard_has(L"\"*\""));
|
|
|
|
|
|
|
|
wcstring wc = L"foo*bar";
|
|
|
|
do_test(wildcard_has(wc) && !wildcard_has_internal(wc));
|
|
|
|
unescape_string_in_place(&wc, UNESCAPE_SPECIAL);
|
|
|
|
do_test(!wildcard_has(wc) && wildcard_has_internal(wc));
|
|
|
|
|
2023-02-03 15:34:29 +00:00
|
|
|
auto feat = mutable_fish_features();
|
|
|
|
auto saved = feat->test(feature_flag_t::qmark_noglob);
|
|
|
|
feat->set(feature_flag_t::qmark_noglob, false);
|
2021-11-27 20:46:15 +00:00
|
|
|
do_test(wildcard_has(L"?"));
|
|
|
|
do_test(!wildcard_has(L"\\?"));
|
2023-02-03 15:34:29 +00:00
|
|
|
feat->set(feature_flag_t::qmark_noglob, true);
|
2021-11-27 20:46:15 +00:00
|
|
|
do_test(!wildcard_has(L"?"));
|
|
|
|
do_test(!wildcard_has(L"\\?"));
|
2023-02-03 15:34:29 +00:00
|
|
|
feat->set(feature_flag_t::qmark_noglob, saved);
|
2021-11-27 20:46:15 +00:00
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_complete() {
|
2013-03-06 04:54:16 +00:00
|
|
|
say(L"Testing complete");
|
2014-08-24 21:28:31 +00:00
|
|
|
|
2021-10-21 20:34:46 +00:00
|
|
|
auto func_props = make_test_func_props();
|
2018-09-11 01:36:04 +00:00
|
|
|
struct test_complete_vars_t : environment_t {
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> get_names(env_mode_flags_t flags) const override {
|
2019-05-29 19:11:08 +00:00
|
|
|
UNUSED(flags);
|
2020-11-29 00:15:01 +00:00
|
|
|
return {L"Foo1", L"Foo2", L"Foo3", L"Bar1", L"Bar2",
|
|
|
|
L"Bar3", L"alpha", L"ALPHA!", L"gamma1", L"GAMMA2"};
|
2018-09-11 01:36:04 +00:00
|
|
|
}
|
2013-03-22 00:44:51 +00:00
|
|
|
|
2018-09-11 01:36:04 +00:00
|
|
|
maybe_t<env_var_t> get(const wcstring &key,
|
|
|
|
env_mode_flags_t mode = ENV_DEFAULT) const override {
|
2019-05-29 19:11:08 +00:00
|
|
|
UNUSED(mode);
|
2018-09-11 05:29:52 +00:00
|
|
|
if (key == L"PWD") {
|
|
|
|
return env_var_t{wgetcwd(), 0};
|
|
|
|
}
|
2018-09-11 01:36:04 +00:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
test_complete_vars_t vars;
|
|
|
|
|
2019-05-05 01:17:18 +00:00
|
|
|
auto parser = parser_t::principal_parser().shared();
|
|
|
|
|
2022-06-08 02:37:31 +00:00
|
|
|
auto do_complete = [&](const wcstring &cmd, completion_request_options_t flags) {
|
2020-01-16 01:14:47 +00:00
|
|
|
return complete(cmd, flags, operation_context_t{parser, vars, no_cancel});
|
2020-01-16 00:41:30 +00:00
|
|
|
};
|
|
|
|
|
2018-09-11 01:36:04 +00:00
|
|
|
completion_list_t completions;
|
2020-01-16 00:41:30 +00:00
|
|
|
|
|
|
|
completions = do_complete(L"$", {});
|
2016-02-28 00:28:47 +00:00
|
|
|
completions_sort_and_prioritize(&completions);
|
2020-11-29 00:15:01 +00:00
|
|
|
do_test(completions.size() == 10);
|
|
|
|
do_test(completions.at(0).completion == L"alpha");
|
|
|
|
do_test(completions.at(1).completion == L"ALPHA!");
|
|
|
|
do_test(completions.at(2).completion == L"Bar1");
|
|
|
|
do_test(completions.at(3).completion == L"Bar2");
|
|
|
|
do_test(completions.at(4).completion == L"Bar3");
|
|
|
|
do_test(completions.at(5).completion == L"Foo1");
|
|
|
|
do_test(completions.at(6).completion == L"Foo2");
|
|
|
|
do_test(completions.at(7).completion == L"Foo3");
|
|
|
|
do_test(completions.at(8).completion == L"gamma1");
|
|
|
|
do_test(completions.at(9).completion == L"GAMMA2");
|
|
|
|
|
|
|
|
// Smartcase test. Lowercase inputs match both lowercase and uppercase.
|
|
|
|
completions = do_complete(L"$a", {});
|
|
|
|
completions_sort_and_prioritize(&completions);
|
|
|
|
do_test(completions.size() == 2);
|
|
|
|
do_test(completions.at(0).completion == L"$ALPHA!");
|
|
|
|
do_test(completions.at(1).completion == L"lpha");
|
2016-02-28 00:28:47 +00:00
|
|
|
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"$F", {});
|
2016-02-28 00:28:47 +00:00
|
|
|
completions_sort_and_prioritize(&completions);
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(completions.size() == 3);
|
|
|
|
do_test(completions.at(0).completion == L"oo1");
|
|
|
|
do_test(completions.at(1).completion == L"oo2");
|
|
|
|
do_test(completions.at(2).completion == L"oo3");
|
2013-06-02 08:14:26 +00:00
|
|
|
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"$1", {});
|
2016-02-28 00:28:47 +00:00
|
|
|
completions_sort_and_prioritize(&completions);
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(completions.empty());
|
2013-06-02 08:14:26 +00:00
|
|
|
|
2022-06-08 02:37:31 +00:00
|
|
|
completion_request_options_t fuzzy_options{};
|
|
|
|
fuzzy_options.fuzzy_match = true;
|
|
|
|
completions = do_complete(L"$1", fuzzy_options);
|
2016-02-28 00:28:47 +00:00
|
|
|
completions_sort_and_prioritize(&completions);
|
2020-11-29 00:15:01 +00:00
|
|
|
do_test(completions.size() == 3);
|
2016-02-28 00:28:47 +00:00
|
|
|
do_test(completions.at(0).completion == L"$Bar1");
|
|
|
|
do_test(completions.at(1).completion == L"$Foo1");
|
2020-11-29 00:15:01 +00:00
|
|
|
do_test(completions.at(2).completion == L"$gamma1");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2017-04-09 04:05:18 +00:00
|
|
|
if (system("mkdir -p 'test/complete_test'")) err(L"mkdir failed");
|
|
|
|
if (system("touch 'test/complete_test/has space'")) err(L"touch failed");
|
2019-09-08 22:39:50 +00:00
|
|
|
if (system("touch 'test/complete_test/bracket[abc]'")) err(L"touch failed");
|
2021-04-21 20:31:58 +00:00
|
|
|
#ifndef __CYGWIN__ // Square brackets are not legal path characters on WIN32/CYGWIN
|
2019-09-08 22:39:50 +00:00
|
|
|
if (system(R"(touch 'test/complete_test/gnarlybracket\[abc]')")) err(L"touch failed");
|
2021-02-22 22:27:45 +00:00
|
|
|
#endif
|
2017-04-09 04:05:18 +00:00
|
|
|
if (system("touch 'test/complete_test/testfile'")) err(L"touch failed");
|
|
|
|
if (system("chmod 700 'test/complete_test/testfile'")) err(L"chmod failed");
|
2021-03-21 08:27:19 +00:00
|
|
|
if (system("mkdir -p 'test/complete_test/foo1'")) err(L"mkdir failed");
|
|
|
|
if (system("mkdir -p 'test/complete_test/foo2'")) err(L"mkdir failed");
|
|
|
|
if (system("mkdir -p 'test/complete_test/foo3'")) err(L"mkdir failed");
|
2015-11-04 07:37:15 +00:00
|
|
|
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo (test/complete_test/testfil", {});
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(completions.size() == 1);
|
2015-11-04 07:37:15 +00:00
|
|
|
do_test(completions.at(0).completion == L"e");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo (ls test/complete_test/testfil", {});
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(completions.size() == 1);
|
2015-11-04 07:37:15 +00:00
|
|
|
do_test(completions.at(0).completion == L"e");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo (command ls test/complete_test/testfil", {});
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(completions.size() == 1);
|
2015-11-04 07:37:15 +00:00
|
|
|
do_test(completions.at(0).completion == L"e");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-07-11 21:03:19 +00:00
|
|
|
// Completing after spaces - see #2447
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo (ls test/complete_test/has\\ ", {});
|
2016-07-11 21:03:19 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"space");
|
|
|
|
|
2019-09-08 22:39:50 +00:00
|
|
|
// Brackets - see #5831
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo (ls test/complete_test/bracket[", {});
|
2019-09-08 22:39:50 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"test/complete_test/bracket[abc]");
|
|
|
|
|
|
|
|
wcstring cmdline = L"touch test/complete_test/bracket[";
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(cmdline, {});
|
2019-09-08 22:39:50 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.front().completion == L"test/complete_test/bracket[abc]");
|
|
|
|
size_t where = cmdline.size();
|
|
|
|
wcstring newcmdline = completion_apply_to_command_line(
|
|
|
|
completions.front().completion, completions.front().flags, cmdline, &where, false);
|
|
|
|
do_test(newcmdline == L"touch test/complete_test/bracket\\[abc\\] ");
|
|
|
|
|
2022-03-21 03:15:02 +00:00
|
|
|
// #8820
|
|
|
|
size_t cursor_pos = 11;
|
|
|
|
newcmdline =
|
|
|
|
completion_apply_to_command_line(L"Debug/", COMPLETE_REPLACES_TOKEN | COMPLETE_NO_SPACE,
|
|
|
|
L"mv debug debug", &cursor_pos, true);
|
|
|
|
do_test(newcmdline == L"mv debug Debug/");
|
|
|
|
|
2021-04-21 20:31:58 +00:00
|
|
|
#ifndef __CYGWIN__ // Square brackets are not legal path characters on WIN32/CYGWIN
|
2019-09-08 22:39:50 +00:00
|
|
|
cmdline = LR"(touch test/complete_test/gnarlybracket\\[)";
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(cmdline, {});
|
2019-09-08 22:39:50 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.front().completion == LR"(test/complete_test/gnarlybracket\[abc])");
|
|
|
|
where = cmdline.size();
|
|
|
|
newcmdline = completion_apply_to_command_line(
|
|
|
|
completions.front().completion, completions.front().flags, cmdline, &where, false);
|
|
|
|
do_test(newcmdline == LR"(touch test/complete_test/gnarlybracket\\\[abc\] )");
|
2021-02-22 22:27:45 +00:00
|
|
|
#endif
|
2019-09-08 22:39:50 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Add a function and test completing it in various ways.
|
2021-10-21 20:56:32 +00:00
|
|
|
function_add(L"scuttlebutt", func_props);
|
2013-05-25 22:41:18 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Complete a function name.
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo (scuttlebut", {});
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"t");
|
2013-10-12 19:04:31 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// But not with the command prefix.
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo (command scuttlebut", {});
|
2021-08-17 23:19:49 +00:00
|
|
|
do_test(completions.empty());
|
2013-06-02 08:14:26 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Not with the builtin prefix.
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo (builtin scuttlebut", {});
|
2021-08-17 23:19:49 +00:00
|
|
|
do_test(completions.empty());
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Not after a redirection.
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo hi > scuttlebut", {});
|
2021-08-17 23:19:49 +00:00
|
|
|
do_test(completions.empty());
|
2014-05-02 17:00:00 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Trailing spaces (#1261).
|
2019-04-25 21:21:06 +00:00
|
|
|
completion_mode_t no_files{};
|
|
|
|
no_files.no_files = true;
|
2022-05-20 17:02:42 +00:00
|
|
|
complete_add(L"foobarbaz", false, wcstring(), option_type_args_only, no_files, {}, L"qux",
|
2021-08-17 22:57:52 +00:00
|
|
|
nullptr, COMPLETE_AUTO_SPACE);
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"foobarbaz ", {});
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"qux");
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Don't complete variable names in single quotes (#1023).
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo '$Foo", {});
|
2014-02-09 23:27:04 +00:00
|
|
|
do_test(completions.empty());
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo \\$Foo", {});
|
2014-02-09 23:27:04 +00:00
|
|
|
do_test(completions.empty());
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// File completions.
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"cat test/complete_test/te", {});
|
2017-04-09 04:05:18 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"stfile");
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo sup > test/complete_test/te", {});
|
2017-04-09 04:05:18 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"stfile");
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"echo sup > test/complete_test/te", {});
|
2017-04-09 04:05:18 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"stfile");
|
2016-05-01 01:37:19 +00:00
|
|
|
|
2017-04-09 04:05:18 +00:00
|
|
|
if (!pushd("test/complete_test")) return;
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"cat te", {});
|
2014-08-24 21:28:31 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"stfile");
|
2018-08-07 09:01:19 +00:00
|
|
|
do_test(!(completions.at(0).flags & COMPLETE_REPLACES_TOKEN));
|
|
|
|
do_test(!(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT));
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"cat testfile te", {});
|
2018-08-07 09:01:19 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"stfile");
|
|
|
|
do_test(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT);
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"cat testfile TE", {});
|
2018-08-07 09:01:19 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"testfile");
|
|
|
|
do_test(completions.at(0).flags & COMPLETE_REPLACES_TOKEN);
|
|
|
|
do_test(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT);
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"something --abc=te", {});
|
2015-07-27 20:43:20 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"stfile");
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"something -abc=te", {});
|
2015-07-27 20:43:20 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"stfile");
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"something abc=te", {});
|
2015-07-27 20:43:20 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"stfile");
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"something abc=stfile", {});
|
2021-08-17 23:19:49 +00:00
|
|
|
do_test(completions.empty());
|
2022-06-08 02:37:31 +00:00
|
|
|
completions = do_complete(L"something abc=stfile", fuzzy_options);
|
2015-07-27 20:43:20 +00:00
|
|
|
do_test(completions.size() == 1);
|
|
|
|
do_test(completions.at(0).completion == L"abc=testfile");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Zero escapes can cause problems. See issue #1631.
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"cat foo\\0", {});
|
2014-08-24 21:28:31 +00:00
|
|
|
do_test(completions.empty());
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"cat foo\\0bar", {});
|
2014-08-24 21:28:31 +00:00
|
|
|
do_test(completions.empty());
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"cat \\0", {});
|
2014-08-24 21:28:31 +00:00
|
|
|
do_test(completions.empty());
|
2020-01-16 00:41:30 +00:00
|
|
|
completions = do_complete(L"cat te\\0", {});
|
2014-08-24 21:28:31 +00:00
|
|
|
do_test(completions.empty());
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2017-04-09 04:05:18 +00:00
|
|
|
popd();
|
|
|
|
completions.clear();
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2018-10-16 06:36:40 +00:00
|
|
|
// Test abbreviations.
|
2021-10-21 20:56:32 +00:00
|
|
|
function_add(L"testabbrsonetwothreefour", func_props);
|
2023-02-24 16:00:05 +00:00
|
|
|
abbrs_get_set()->add(L"somename", L"testabbrsonetwothreezero", L"expansion",
|
|
|
|
abbrs_position_t::command, false);
|
2020-01-16 01:14:47 +00:00
|
|
|
completions = complete(L"testabbrsonetwothree", {}, parser->context());
|
2018-10-16 06:36:40 +00:00
|
|
|
do_test(completions.size() == 2);
|
|
|
|
do_test(completions.at(0).completion == L"four");
|
|
|
|
do_test((completions.at(0).flags & COMPLETE_NO_SPACE) == 0);
|
|
|
|
// Abbreviations should not have a space after them.
|
|
|
|
do_test(completions.at(1).completion == L"zero");
|
|
|
|
do_test((completions.at(1).flags & COMPLETE_NO_SPACE) != 0);
|
2022-07-15 07:03:45 +00:00
|
|
|
abbrs_get_set()->erase(L"testabbrsonetwothreezero");
|
2018-10-16 06:36:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Test wraps.
|
2019-03-14 22:12:14 +00:00
|
|
|
do_test(comma_join(complete_get_wrap_targets(L"wrapper1")).empty());
|
2014-08-16 01:14:36 +00:00
|
|
|
complete_add_wrapper(L"wrapper1", L"wrapper2");
|
2018-02-27 03:21:46 +00:00
|
|
|
do_test(comma_join(complete_get_wrap_targets(L"wrapper1")) == L"wrapper2");
|
2014-08-16 01:14:36 +00:00
|
|
|
complete_add_wrapper(L"wrapper2", L"wrapper3");
|
2018-02-27 03:21:46 +00:00
|
|
|
do_test(comma_join(complete_get_wrap_targets(L"wrapper1")) == L"wrapper2");
|
|
|
|
do_test(comma_join(complete_get_wrap_targets(L"wrapper2")) == L"wrapper3");
|
2016-05-01 01:37:19 +00:00
|
|
|
complete_add_wrapper(L"wrapper3", L"wrapper1"); // loop!
|
2018-02-27 03:21:46 +00:00
|
|
|
do_test(comma_join(complete_get_wrap_targets(L"wrapper1")) == L"wrapper2");
|
|
|
|
do_test(comma_join(complete_get_wrap_targets(L"wrapper2")) == L"wrapper3");
|
|
|
|
do_test(comma_join(complete_get_wrap_targets(L"wrapper3")) == L"wrapper1");
|
2014-08-16 01:14:36 +00:00
|
|
|
complete_remove_wrapper(L"wrapper1", L"wrapper2");
|
2019-03-14 22:12:14 +00:00
|
|
|
do_test(comma_join(complete_get_wrap_targets(L"wrapper1")).empty());
|
2018-02-27 03:21:46 +00:00
|
|
|
do_test(comma_join(complete_get_wrap_targets(L"wrapper2")) == L"wrapper3");
|
|
|
|
do_test(comma_join(complete_get_wrap_targets(L"wrapper3")) == L"wrapper1");
|
2021-03-21 08:27:19 +00:00
|
|
|
|
|
|
|
// Test cd wrapping chain
|
|
|
|
if (!pushd("test/complete_test")) err(L"pushd(\"test/complete_test\") failed");
|
|
|
|
|
|
|
|
complete_add_wrapper(L"cdwrap1", L"cd");
|
|
|
|
complete_add_wrapper(L"cdwrap2", L"cdwrap1");
|
|
|
|
|
|
|
|
completion_list_t cd_compl = do_complete(L"cd ", {});
|
|
|
|
completions_sort_and_prioritize(&cd_compl);
|
|
|
|
|
|
|
|
completion_list_t cdwrap1_compl = do_complete(L"cdwrap1 ", {});
|
|
|
|
completions_sort_and_prioritize(&cdwrap1_compl);
|
|
|
|
|
|
|
|
completion_list_t cdwrap2_compl = do_complete(L"cdwrap2 ", {});
|
|
|
|
completions_sort_and_prioritize(&cdwrap2_compl);
|
|
|
|
|
2021-03-21 18:44:05 +00:00
|
|
|
size_t min_compl_size =
|
|
|
|
std::min(cd_compl.size(), std::min(cdwrap1_compl.size(), cdwrap2_compl.size()));
|
2021-03-21 08:27:19 +00:00
|
|
|
|
|
|
|
do_test(cd_compl.size() == min_compl_size);
|
|
|
|
do_test(cdwrap1_compl.size() == min_compl_size);
|
2021-03-21 18:44:05 +00:00
|
|
|
do_test(cdwrap2_compl.size() == min_compl_size);
|
2021-03-21 08:27:19 +00:00
|
|
|
for (size_t i = 0; i < min_compl_size; ++i) {
|
|
|
|
do_test(cd_compl[i].completion == cdwrap1_compl[i].completion);
|
|
|
|
do_test(cdwrap1_compl[i].completion == cdwrap2_compl[i].completion);
|
|
|
|
}
|
|
|
|
|
|
|
|
complete_remove_wrapper(L"cdwrap1", L"cd");
|
|
|
|
complete_remove_wrapper(L"cdwrap2", L"cdwrap1");
|
|
|
|
popd();
|
2013-03-06 04:54:16 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_1_completion(wcstring line, const wcstring &completion, complete_flags_t flags,
|
|
|
|
bool append_only, wcstring expected, long source_line) {
|
|
|
|
// str is given with a caret, which we use to represent the cursor position. Find it.
|
2013-02-02 22:50:22 +00:00
|
|
|
const size_t in_cursor_pos = line.find(L'^');
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(in_cursor_pos != wcstring::npos);
|
2013-02-02 22:50:22 +00:00
|
|
|
line.erase(in_cursor_pos, 1);
|
2013-02-03 19:38:22 +00:00
|
|
|
|
2013-02-02 22:50:22 +00:00
|
|
|
const size_t out_cursor_pos = expected.find(L'^');
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(out_cursor_pos != wcstring::npos);
|
2013-02-02 22:50:22 +00:00
|
|
|
expected.erase(out_cursor_pos, 1);
|
2013-02-03 19:38:22 +00:00
|
|
|
|
2013-02-02 22:50:22 +00:00
|
|
|
size_t cursor_pos = in_cursor_pos;
|
2016-05-01 01:37:19 +00:00
|
|
|
wcstring result =
|
|
|
|
completion_apply_to_command_line(completion, flags, line, &cursor_pos, append_only);
|
|
|
|
if (result != expected) {
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, L"line %ld: %ls + %ls -> [%ls], expected [%ls]\n", source_line,
|
2019-05-05 10:09:25 +00:00
|
|
|
line.c_str(), completion.c_str(), result.c_str(), expected.c_str());
|
2013-02-02 22:50:22 +00:00
|
|
|
}
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(result == expected);
|
|
|
|
do_test(cursor_pos == out_cursor_pos);
|
2013-02-02 22:50:22 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_completion_insertions() {
|
2013-02-03 19:38:22 +00:00
|
|
|
#define TEST_1_COMPLETION(a, b, c, d, e) test_1_completion(a, b, c, d, e, __LINE__)
|
2013-03-06 04:54:16 +00:00
|
|
|
say(L"Testing completion insertions");
|
2013-02-02 22:50:22 +00:00
|
|
|
TEST_1_COMPLETION(L"foo^", L"bar", 0, false, L"foobar ^");
|
2019-09-10 18:42:41 +00:00
|
|
|
// An unambiguous completion of a token that is already trailed by a space character.
|
|
|
|
// After completing, the cursor moves on to the next token, suggesting to the user that the
|
|
|
|
// current token is finished.
|
|
|
|
TEST_1_COMPLETION(L"foo^ baz", L"bar", 0, false, L"foobar ^baz");
|
2013-02-02 22:50:22 +00:00
|
|
|
TEST_1_COMPLETION(L"'foo^", L"bar", 0, false, L"'foobar' ^");
|
|
|
|
TEST_1_COMPLETION(L"'foo'^", L"bar", 0, false, L"'foobar' ^");
|
|
|
|
TEST_1_COMPLETION(L"'foo\\'^", L"bar", 0, false, L"'foo\\'bar' ^");
|
|
|
|
TEST_1_COMPLETION(L"foo\\'^", L"bar", 0, false, L"foo\\'bar ^");
|
2013-02-03 19:38:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Test append only.
|
2013-02-02 22:50:22 +00:00
|
|
|
TEST_1_COMPLETION(L"foo^", L"bar", 0, true, L"foobar ^");
|
2019-09-10 18:42:41 +00:00
|
|
|
TEST_1_COMPLETION(L"foo^ baz", L"bar", 0, true, L"foobar ^baz");
|
2013-02-02 22:50:22 +00:00
|
|
|
TEST_1_COMPLETION(L"'foo^", L"bar", 0, true, L"'foobar' ^");
|
|
|
|
TEST_1_COMPLETION(L"'foo'^", L"bar", 0, true, L"'foo'bar ^");
|
|
|
|
TEST_1_COMPLETION(L"'foo\\'^", L"bar", 0, true, L"'foo\\'bar' ^");
|
|
|
|
TEST_1_COMPLETION(L"foo\\'^", L"bar", 0, true, L"foo\\'bar ^");
|
2013-02-03 19:38:22 +00:00
|
|
|
|
2013-02-02 22:50:22 +00:00
|
|
|
TEST_1_COMPLETION(L"foo^", L"bar", COMPLETE_NO_SPACE, false, L"foobar^");
|
|
|
|
TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_NO_SPACE, false, L"'foobar^");
|
|
|
|
TEST_1_COMPLETION(L"'foo'^", L"bar", COMPLETE_NO_SPACE, false, L"'foobar'^");
|
|
|
|
TEST_1_COMPLETION(L"'foo\\'^", L"bar", COMPLETE_NO_SPACE, false, L"'foo\\'bar^");
|
|
|
|
TEST_1_COMPLETION(L"foo\\'^", L"bar", COMPLETE_NO_SPACE, false, L"foo\\'bar^");
|
|
|
|
|
2013-05-25 22:41:18 +00:00
|
|
|
TEST_1_COMPLETION(L"foo^", L"bar", COMPLETE_REPLACES_TOKEN, false, L"bar ^");
|
|
|
|
TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_REPLACES_TOKEN, false, L"bar ^");
|
2019-09-22 21:06:14 +00:00
|
|
|
|
|
|
|
// See #6130
|
|
|
|
TEST_1_COMPLETION(L": (:^ ''", L"", 0, false, L": (: ^''");
|
2013-02-02 22:50:22 +00:00
|
|
|
}
|
|
|
|
|
2018-01-12 19:15:35 +00:00
|
|
|
static void perform_one_autosuggestion_cd_test(const wcstring &command, const wcstring &expected,
|
2018-09-22 04:52:47 +00:00
|
|
|
const environment_t &vars, long line) {
|
2020-01-16 00:41:30 +00:00
|
|
|
completion_list_t comps =
|
2022-06-08 02:37:31 +00:00
|
|
|
complete(command, completion_request_options_t::autosuggest(), operation_context_t{vars});
|
2016-05-01 01:37:19 +00:00
|
|
|
|
2016-02-17 05:43:00 +00:00
|
|
|
bool expects_error = (expected == L"<error>");
|
2016-05-01 01:37:19 +00:00
|
|
|
|
|
|
|
if (comps.empty() && !expects_error) {
|
2019-05-05 10:09:25 +00:00
|
|
|
std::fwprintf(stderr, L"line %ld: autosuggest_suggest_special() failed for command %ls\n",
|
|
|
|
line, command.c_str());
|
2017-04-09 04:05:18 +00:00
|
|
|
do_test_from(!comps.empty(), line);
|
2016-02-08 09:29:23 +00:00
|
|
|
return;
|
2016-05-01 01:37:19 +00:00
|
|
|
} else if (!comps.empty() && expects_error) {
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr,
|
2019-05-05 10:09:25 +00:00
|
|
|
L"line %ld: autosuggest_suggest_special() was expected to fail but did not, "
|
|
|
|
L"for command %ls\n",
|
|
|
|
line, command.c_str());
|
2017-04-09 04:05:18 +00:00
|
|
|
do_test_from(comps.empty(), line);
|
2016-02-17 05:43:00 +00:00
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
|
|
|
|
if (!comps.empty()) {
|
2016-02-17 05:43:00 +00:00
|
|
|
completions_sort_and_prioritize(&comps);
|
|
|
|
const completion_t &suggestion = comps.at(0);
|
2016-05-01 01:37:19 +00:00
|
|
|
|
|
|
|
if (suggestion.completion != expected) {
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(
|
2017-01-14 04:34:15 +00:00
|
|
|
stderr,
|
|
|
|
L"line %ld: complete() for cd returned the wrong expected string for command %ls\n",
|
2016-05-01 01:37:19 +00:00
|
|
|
line, command.c_str());
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, L" actual: %ls\n", suggestion.completion.c_str());
|
|
|
|
std::fwprintf(stderr, L"expected: %ls\n", expected.c_str());
|
2017-04-09 04:05:18 +00:00
|
|
|
do_test_from(suggestion.completion == expected, line);
|
2016-02-17 05:43:00 +00:00
|
|
|
}
|
2016-02-08 09:29:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-12 19:15:35 +00:00
|
|
|
static void perform_one_completion_cd_test(const wcstring &command, const wcstring &expected,
|
2018-09-25 04:17:05 +00:00
|
|
|
const environment_t &vars, long line) {
|
2022-01-27 03:06:09 +00:00
|
|
|
completion_list_t comps = complete(
|
|
|
|
command, {}, operation_context_t{parser_t::principal_parser().shared(), vars, no_cancel});
|
2018-01-08 21:36:20 +00:00
|
|
|
|
|
|
|
bool expects_error = (expected == L"<error>");
|
|
|
|
|
|
|
|
if (comps.empty() && !expects_error) {
|
2019-05-05 10:09:25 +00:00
|
|
|
std::fwprintf(stderr, L"line %ld: autosuggest_suggest_special() failed for command %ls\n",
|
|
|
|
line, command.c_str());
|
2018-01-08 21:36:20 +00:00
|
|
|
do_test_from(!comps.empty(), line);
|
|
|
|
return;
|
|
|
|
} else if (!comps.empty() && expects_error) {
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr,
|
2019-05-05 10:09:25 +00:00
|
|
|
L"line %ld: autosuggest_suggest_special() was expected to fail but did not, "
|
|
|
|
L"for command %ls\n",
|
|
|
|
line, command.c_str());
|
2018-01-08 21:36:20 +00:00
|
|
|
do_test_from(comps.empty(), line);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!comps.empty()) {
|
|
|
|
completions_sort_and_prioritize(&comps);
|
|
|
|
const completion_t &suggestion = comps.at(0);
|
|
|
|
|
|
|
|
if (suggestion.completion != expected) {
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr,
|
2019-05-05 10:09:25 +00:00
|
|
|
L"line %ld: complete() for cd tab completion returned the wrong expected "
|
|
|
|
L"string for command %ls\n",
|
|
|
|
line, command.c_str());
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, L" actual: %ls\n", suggestion.completion.c_str());
|
|
|
|
std::fwprintf(stderr, L"expected: %ls\n", expected.c_str());
|
2018-01-08 21:36:20 +00:00
|
|
|
do_test_from(suggestion.completion == expected, line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Testing test_autosuggest_suggest_special, in particular for properly handling quotes and
|
|
|
|
// backslashes.
|
|
|
|
static void test_autosuggest_suggest_special() {
|
2022-10-24 20:54:46 +00:00
|
|
|
// We execute LSAN with use_tls=0 under CI to avoid a SIGSEGV crash in LSAN itself.
|
|
|
|
// Unfortunately, this causes it to incorrectly flag a memory leak here that doesn't reproduce
|
|
|
|
// locally with use_tls=1.
|
|
|
|
#ifdef FISH_CI_SAN
|
|
|
|
__lsan::ScopedDisabler disable_leak_detection{};
|
|
|
|
#endif
|
|
|
|
|
2017-04-09 04:05:18 +00:00
|
|
|
if (system("mkdir -p 'test/autosuggest_test/0foobar'")) err(L"mkdir failed");
|
|
|
|
if (system("mkdir -p 'test/autosuggest_test/1foo bar'")) err(L"mkdir failed");
|
|
|
|
if (system("mkdir -p 'test/autosuggest_test/2foo bar'")) err(L"mkdir failed");
|
2021-12-30 19:39:29 +00:00
|
|
|
#ifndef __CYGWIN__
|
|
|
|
// Cygwin disallows backslashes in filenames.
|
2017-04-09 04:05:18 +00:00
|
|
|
if (system("mkdir -p 'test/autosuggest_test/3foo\\bar'")) err(L"mkdir failed");
|
2021-12-30 19:39:29 +00:00
|
|
|
#endif
|
2017-04-16 05:28:59 +00:00
|
|
|
if (system("mkdir -p test/autosuggest_test/4foo\\'bar")) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"mkdir failed"); // a path with a single quote
|
2017-04-16 05:28:59 +00:00
|
|
|
}
|
|
|
|
if (system("mkdir -p test/autosuggest_test/5foo\\\"bar")) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"mkdir failed"); // a path with a double quote
|
2017-04-16 05:28:59 +00:00
|
|
|
}
|
|
|
|
// This is to ensure tilde expansion is handled. See the `cd ~/test_autosuggest_suggest_specia`
|
2019-01-05 13:52:26 +00:00
|
|
|
// test below.
|
|
|
|
// Fake out the home directory
|
2018-09-14 07:36:26 +00:00
|
|
|
parser_t::principal_parser().vars().set_one(L"HOME", ENV_LOCAL | ENV_EXPORT, L"test/test-home");
|
2019-01-05 13:52:26 +00:00
|
|
|
if (system("mkdir -p test/test-home/test_autosuggest_suggest_special/")) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"mkdir failed");
|
2017-04-16 05:28:59 +00:00
|
|
|
}
|
|
|
|
if (system("mkdir -p test/autosuggest_test/start/unique2/unique3/multi4")) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"mkdir failed");
|
2017-04-16 05:28:59 +00:00
|
|
|
}
|
|
|
|
if (system("mkdir -p test/autosuggest_test/start/unique2/unique3/multi42")) {
|
|
|
|
err(L"mkdir failed");
|
|
|
|
}
|
|
|
|
if (system("mkdir -p test/autosuggest_test/start/unique2/.hiddenDir/moreStuff")) {
|
|
|
|
err(L"mkdir failed");
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
|
2022-09-27 02:01:30 +00:00
|
|
|
// Ensure symlink don't cause us to chase endlessly.
|
|
|
|
if (system("mkdir -p test/autosuggest_test/has_loop/loopy")) {
|
|
|
|
err(L"mkdir failed");
|
|
|
|
}
|
|
|
|
(void)unlink("test/autosuggest_test/has_loop/loopy/loop");
|
|
|
|
if (symlink("../loopy", "test/autosuggest_test/has_loop/loopy/loop")) {
|
|
|
|
err(L"symlink failed");
|
|
|
|
}
|
|
|
|
|
2017-04-09 04:05:18 +00:00
|
|
|
const wcstring wd = L"test/autosuggest_test";
|
2016-05-01 01:37:19 +00:00
|
|
|
|
2018-09-22 04:52:47 +00:00
|
|
|
pwd_environment_t vars{};
|
2020-12-30 08:40:56 +00:00
|
|
|
vars.vars[L"HOME"] = parser_t::principal_parser().vars().get(L"HOME")->as_string();
|
2016-05-01 01:37:19 +00:00
|
|
|
|
2018-09-22 04:52:47 +00:00
|
|
|
perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/0", L"foobar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/0", L"foobar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/0", L"foobar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/1", L"foo bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/1", L"foo bar/", vars,
|
|
|
|
__LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/1", L"foo bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/2", L"foo bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/2", L"foo bar/", vars,
|
|
|
|
__LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/2", L"foo bar/", vars,
|
|
|
|
__LINE__);
|
2021-12-30 19:39:29 +00:00
|
|
|
#ifndef __CYGWIN__
|
2018-09-22 04:52:47 +00:00
|
|
|
perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/3", L"foo\\bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/3", L"foo\\bar/", vars,
|
|
|
|
__LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/3", L"foo\\bar/", vars,
|
2017-08-18 23:52:39 +00:00
|
|
|
__LINE__);
|
2021-12-30 19:39:29 +00:00
|
|
|
#endif
|
2018-09-22 04:52:47 +00:00
|
|
|
perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/4", L"foo'bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/4", L"foo'bar/", vars,
|
|
|
|
__LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/4", L"foo'bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/5", L"foo\"bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/5", L"foo\"bar/", vars,
|
|
|
|
__LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/5", L"foo\"bar/", vars,
|
|
|
|
__LINE__);
|
|
|
|
|
2020-12-30 08:40:56 +00:00
|
|
|
vars.vars[L"AUTOSUGGEST_TEST_LOC"] = wd;
|
2018-09-22 04:52:47 +00:00
|
|
|
perform_one_autosuggestion_cd_test(L"cd $AUTOSUGGEST_TEST_LOC/0", L"foobar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd ~/test_autosuggest_suggest_specia", L"l/", vars,
|
|
|
|
__LINE__);
|
|
|
|
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/start/", L"unique2/unique3/",
|
|
|
|
vars, __LINE__);
|
2016-02-08 09:29:23 +00:00
|
|
|
|
2022-09-27 02:01:30 +00:00
|
|
|
perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/has_loop/", L"loopy/loop/", vars,
|
|
|
|
__LINE__);
|
|
|
|
|
2023-04-01 11:50:30 +00:00
|
|
|
if (!pushd(wcs2zstring(wd).c_str())) return;
|
2018-09-22 04:52:47 +00:00
|
|
|
perform_one_autosuggestion_cd_test(L"cd 0", L"foobar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"0", L"foobar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd '0", L"foobar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd 1", L"foo bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"1", L"foo bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd '1", L"foo bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd 2", L"foo bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"2", L"foo bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd '2", L"foo bar/", vars, __LINE__);
|
2021-12-30 19:39:29 +00:00
|
|
|
#ifndef __CYGWIN__
|
2018-09-22 04:52:47 +00:00
|
|
|
perform_one_autosuggestion_cd_test(L"cd 3", L"foo\\bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"3", L"foo\\bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd '3", L"foo\\bar/", vars, __LINE__);
|
2021-12-30 19:39:29 +00:00
|
|
|
#endif
|
2018-09-22 04:52:47 +00:00
|
|
|
perform_one_autosuggestion_cd_test(L"cd 4", L"foo'bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"4", L"foo'bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd '4", L"foo'bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd 5", L"foo\"bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd \"5", L"foo\"bar/", vars, __LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd '5", L"foo\"bar/", vars, __LINE__);
|
2016-05-01 01:37:19 +00:00
|
|
|
|
|
|
|
// A single quote should defeat tilde expansion.
|
2018-09-22 04:52:47 +00:00
|
|
|
perform_one_autosuggestion_cd_test(L"cd '~/test_autosuggest_suggest_specia'", L"<error>", vars,
|
2016-05-01 01:37:19 +00:00
|
|
|
__LINE__);
|
|
|
|
|
2017-04-09 04:05:18 +00:00
|
|
|
// Don't crash on ~ (issue #2696). Note this is cwd dependent.
|
2020-03-26 05:59:32 +00:00
|
|
|
if (system("mkdir -p '~absolutelynosuchuser/path1/path2/'")) err(L"mkdir failed");
|
2020-04-21 17:24:33 +00:00
|
|
|
perform_one_autosuggestion_cd_test(L"cd ~absolutelynosuchus", L"er/path1/path2/", vars,
|
|
|
|
__LINE__);
|
|
|
|
perform_one_autosuggestion_cd_test(L"cd ~absolutelynosuchuser/", L"path1/path2/", vars,
|
|
|
|
__LINE__);
|
2020-03-26 05:59:32 +00:00
|
|
|
perform_one_completion_cd_test(L"cd ~absolutelynosuchus", L"er/", vars, __LINE__);
|
|
|
|
perform_one_completion_cd_test(L"cd ~absolutelynosuchuser/", L"path1/", vars, __LINE__);
|
2016-02-17 05:43:00 +00:00
|
|
|
|
2018-09-11 03:57:10 +00:00
|
|
|
parser_t::principal_parser().vars().remove(L"HOME", ENV_LOCAL | ENV_EXPORT);
|
2017-04-09 04:05:18 +00:00
|
|
|
popd();
|
2012-02-19 07:26:39 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 02:52:02 +00:00
|
|
|
static void perform_one_autosuggestion_should_ignore_test(const wcstring &command, long line) {
|
2022-06-08 02:37:31 +00:00
|
|
|
completion_list_t comps = complete(command, completion_request_options_t::autosuggest(),
|
|
|
|
operation_context_t::empty());
|
2014-08-24 21:28:31 +00:00
|
|
|
do_test(comps.empty());
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!comps.empty()) {
|
2014-08-24 21:28:31 +00:00
|
|
|
const wcstring &suggestion = comps.front().completion;
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, L"line %ld: complete() expected to return nothing for %ls\n", line,
|
2019-05-05 10:09:25 +00:00
|
|
|
command.c_str());
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, L" instead got: %ls\n", suggestion.c_str());
|
2014-08-24 21:28:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_autosuggestion_ignores() {
|
2014-08-24 21:28:31 +00:00
|
|
|
say(L"Testing scenarios that should produce no autosuggestions");
|
2016-05-01 01:37:19 +00:00
|
|
|
// Do not do file autosuggestions immediately after certain statement terminators - see #1631.
|
2016-10-10 02:52:02 +00:00
|
|
|
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST|", __LINE__);
|
|
|
|
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST&", __LINE__);
|
|
|
|
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST#comment", __LINE__);
|
|
|
|
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST;", __LINE__);
|
2014-08-24 21:28:31 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_autosuggestion_combining() {
|
2013-01-05 23:21:42 +00:00
|
|
|
say(L"Testing autosuggestion combining");
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(combine_command_and_autosuggestion(L"alpha", L"alphabeta") == L"alphabeta");
|
2013-01-08 10:39:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// When the last token contains no capital letters, we use the case of the autosuggestion.
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(combine_command_and_autosuggestion(L"alpha", L"ALPHABETA") == L"ALPHABETA");
|
2013-01-08 10:39:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// When the last token contains capital letters, we use its case.
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(combine_command_and_autosuggestion(L"alPha", L"alphabeTa") == L"alPhabeTa");
|
2013-01-08 10:39:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// If autosuggestion is not longer than input, use the input's case.
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(combine_command_and_autosuggestion(L"alpha", L"ALPHAA") == L"ALPHAA");
|
|
|
|
do_test(combine_command_and_autosuggestion(L"alpha", L"ALPHA") == L"alpha");
|
2013-01-05 23:21:42 +00:00
|
|
|
}
|
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
static void test_history_matches(history_search_t &search, const std::vector<wcstring> &expected,
|
2018-08-12 07:30:57 +00:00
|
|
|
unsigned from_line) {
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> found;
|
2020-02-09 17:39:14 +00:00
|
|
|
while (search.go_to_next_match(history_search_direction_t::backward)) {
|
2018-08-12 07:30:57 +00:00
|
|
|
found.push_back(search.current_string());
|
2012-02-06 00:42:24 +00:00
|
|
|
}
|
2018-08-12 07:30:57 +00:00
|
|
|
do_test_from(expected == found, from_line);
|
|
|
|
if (expected != found) {
|
|
|
|
fprintf(stderr, "Expected %ls, found %ls\n", comma_join(expected).c_str(),
|
|
|
|
comma_join(found).c_str());
|
2012-02-06 00:42:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static bool history_contains(history_t *history, const wcstring &txt) {
|
2012-04-17 03:26:50 +00:00
|
|
|
bool result = false;
|
|
|
|
size_t i;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (i = 1;; i++) {
|
2012-04-17 03:26:50 +00:00
|
|
|
history_item_t item = history->item_at_index(i);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (item.empty()) break;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
if (item.str() == txt) {
|
2012-04-17 03:26:50 +00:00
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-01-10 00:22:32 +00:00
|
|
|
static bool history_contains(const std::shared_ptr<history_t> &history, const wcstring &txt) {
|
2017-01-22 01:01:38 +00:00
|
|
|
return history_contains(history.get(), txt);
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_input() {
|
2014-02-12 20:49:32 +00:00
|
|
|
say(L"Testing input");
|
2019-06-02 23:41:13 +00:00
|
|
|
inputter_t input{parser_t::principal_parser()};
|
2016-05-01 01:37:19 +00:00
|
|
|
// Ensure sequences are order independent. Here we add two bindings where the first is a prefix
|
|
|
|
// of the second, and then emit the second key list. The second binding should be invoked, not
|
|
|
|
// the first!
|
2014-02-12 20:49:32 +00:00
|
|
|
wcstring prefix_binding = L"qqqqqqqa";
|
|
|
|
wcstring desired_binding = prefix_binding + L'a';
|
2019-06-03 05:36:11 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
auto input_mapping = input_mappings();
|
2020-06-06 22:16:50 +00:00
|
|
|
input_mapping->add(prefix_binding, L"up-line");
|
|
|
|
input_mapping->add(desired_binding, L"down-line");
|
2019-06-03 05:36:11 +00:00
|
|
|
}
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Push the desired binding to the queue.
|
2020-06-30 22:29:06 +00:00
|
|
|
for (wchar_t c : desired_binding) {
|
2021-04-11 02:49:56 +00:00
|
|
|
input.queue_char(c);
|
2014-02-12 20:49:32 +00:00
|
|
|
}
|
2014-03-23 06:46:58 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Now test.
|
2021-04-11 02:49:56 +00:00
|
|
|
auto evt = input.read_char();
|
2019-03-16 23:48:23 +00:00
|
|
|
if (!evt.is_readline()) {
|
|
|
|
err(L"Event is not a readline");
|
2019-03-24 00:32:39 +00:00
|
|
|
} else if (evt.get_readline() != readline_cmd_t::down_line) {
|
|
|
|
err(L"Expected to read char down_line");
|
2014-02-12 20:49:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-04 11:47:44 +00:00
|
|
|
static void test_undo() {
|
|
|
|
say(L"Testing undo/redo setting and restoring text and cursor position.");
|
|
|
|
|
|
|
|
editable_line_t line;
|
|
|
|
do_test(!line.undo()); // nothing to undo
|
2021-08-17 23:19:49 +00:00
|
|
|
do_test(line.text().empty());
|
2020-02-04 11:47:44 +00:00
|
|
|
do_test(line.position() == 0);
|
2022-09-14 12:03:39 +00:00
|
|
|
line.push_edit(edit_t(0, 0, L"a b c"), true);
|
2020-02-04 11:47:44 +00:00
|
|
|
do_test(line.text() == L"a b c");
|
|
|
|
do_test(line.position() == 5);
|
|
|
|
line.set_position(2);
|
2022-09-14 12:03:39 +00:00
|
|
|
line.push_edit(edit_t(2, 1, L"B"), true); // replacement right of cursor
|
2020-02-04 11:47:44 +00:00
|
|
|
do_test(line.text() == L"a B c");
|
|
|
|
line.undo();
|
|
|
|
do_test(line.text() == L"a b c");
|
|
|
|
do_test(line.position() == 2);
|
|
|
|
line.redo();
|
|
|
|
do_test(line.text() == L"a B c");
|
|
|
|
do_test(line.position() == 3);
|
|
|
|
|
|
|
|
do_test(!line.redo()); // nothing to redo
|
|
|
|
|
2022-09-14 12:03:39 +00:00
|
|
|
line.push_edit(edit_t(0, 2, L""), true); // deletion left of cursor
|
2020-02-04 11:47:44 +00:00
|
|
|
do_test(line.text() == L"B c");
|
|
|
|
do_test(line.position() == 1);
|
|
|
|
line.undo();
|
|
|
|
do_test(line.text() == L"a B c");
|
|
|
|
do_test(line.position() == 3);
|
|
|
|
line.redo();
|
|
|
|
do_test(line.text() == L"B c");
|
|
|
|
do_test(line.position() == 1);
|
|
|
|
|
2022-09-14 12:03:39 +00:00
|
|
|
line.push_edit(edit_t(0, line.size(), L"a b c"), true); // replacement left and right of cursor
|
2020-02-04 11:47:44 +00:00
|
|
|
do_test(line.text() == L"a b c");
|
|
|
|
do_test(line.position() == 5);
|
|
|
|
|
|
|
|
say(L"Testing undoing coalesced edits.");
|
|
|
|
line.clear();
|
2022-09-14 12:03:39 +00:00
|
|
|
line.push_edit(edit_t(line.position(), 0, L"a"), true);
|
|
|
|
line.push_edit(edit_t(line.position(), 0, L"b"), true);
|
|
|
|
line.push_edit(edit_t(line.position(), 0, L"c"), true);
|
|
|
|
line.push_edit(edit_t(line.position(), 0, L" "), true);
|
2020-02-04 11:47:44 +00:00
|
|
|
line.undo();
|
|
|
|
line.undo();
|
|
|
|
line.redo();
|
|
|
|
do_test(line.text() == L"abc");
|
2020-02-09 17:39:14 +00:00
|
|
|
// This removes the space insertion from the history, but does not coalesce with the first edit.
|
2022-09-14 12:03:39 +00:00
|
|
|
line.push_edit(edit_t(line.position(), 0, L"d"), true);
|
|
|
|
line.push_edit(edit_t(line.position(), 0, L"e"), true);
|
2020-02-04 11:47:44 +00:00
|
|
|
do_test(line.text() == L"abcde");
|
|
|
|
line.undo();
|
|
|
|
do_test(line.text() == L"abc");
|
|
|
|
}
|
|
|
|
|
2022-09-20 18:58:37 +00:00
|
|
|
#define UVARS_PER_THREAD 8
|
|
|
|
#define UVARS_TEST_PATH L"test/fish_uvars_test/varsfile.txt"
|
2014-04-27 20:34:51 +00:00
|
|
|
|
2017-01-23 19:45:28 +00:00
|
|
|
static int test_universal_helper(int x) {
|
2017-07-14 17:45:31 +00:00
|
|
|
callback_data_list_t callbacks;
|
2021-05-10 19:26:30 +00:00
|
|
|
env_universal_t uvars;
|
|
|
|
uvars.initialize_at_path(callbacks, UVARS_TEST_PATH);
|
2016-05-01 01:37:19 +00:00
|
|
|
for (int j = 0; j < UVARS_PER_THREAD; j++) {
|
2017-01-23 19:45:28 +00:00
|
|
|
const wcstring key = format_string(L"key_%d_%d", x, j);
|
|
|
|
const wcstring val = format_string(L"val_%d_%d", x, j);
|
2018-10-22 07:49:09 +00:00
|
|
|
uvars.set(key, env_var_t{val, 0});
|
2017-07-14 17:45:31 +00:00
|
|
|
bool synced = uvars.sync(callbacks);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!synced) {
|
Attempt to fix the sporadic uvar test failures on Linux
We identify when the universal variable file has changed out from under us by
comparing a bunch of fields from its stat: inode, device, size, high-precision
timestamp, generation. Linux aggressively reuses inodes, and the size may be
the same by coincidence (which is the case in the tests). Also, Linux
officially has nanosecond precision, but in practice it seems to only uses
millisecond precision for storing mtimes. Thus if there are three or more
updates within a millisecond, every field we check may be the same, and we are
vulnerable to the ABA problem. I believe this explains the occasional test
failures.
The solution is to manually set the nanosecond field of the mtime timestamp to
something unlikely to be duplicated, like a random number, or better yet, the
current time (with nanosecond precision). This is more in the spirit of the
timestamp, and it means we're around a million times less likely to collide.
This seems to fix the tests.
2015-11-09 07:48:32 +00:00
|
|
|
err(L"Failed to sync universal variables after modification");
|
2014-04-27 20:34:51 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Last step is to delete the first key.
|
2017-01-23 19:45:28 +00:00
|
|
|
uvars.remove(format_string(L"key_%d_%d", x, 0));
|
2017-07-14 17:45:31 +00:00
|
|
|
bool synced = uvars.sync(callbacks);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!synced) {
|
Attempt to fix the sporadic uvar test failures on Linux
We identify when the universal variable file has changed out from under us by
comparing a bunch of fields from its stat: inode, device, size, high-precision
timestamp, generation. Linux aggressively reuses inodes, and the size may be
the same by coincidence (which is the case in the tests). Also, Linux
officially has nanosecond precision, but in practice it seems to only uses
millisecond precision for storing mtimes. Thus if there are three or more
updates within a millisecond, every field we check may be the same, and we are
vulnerable to the ABA problem. I believe this explains the occasional test
failures.
The solution is to manually set the nanosecond field of the mtime timestamp to
something unlikely to be duplicated, like a random number, or better yet, the
current time (with nanosecond precision). This is more in the spirit of the
timestamp, and it means we're around a million times less likely to collide.
This seems to fix the tests.
2015-11-09 07:48:32 +00:00
|
|
|
err(L"Failed to sync universal variables after deletion");
|
2014-04-30 00:03:00 +00:00
|
|
|
}
|
2014-04-27 20:34:51 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_universal() {
|
2014-04-27 20:34:51 +00:00
|
|
|
say(L"Testing universal variables");
|
2017-04-09 04:05:18 +00:00
|
|
|
if (system("mkdir -p test/fish_uvars_test/")) err(L"mkdir failed");
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2017-08-28 09:51:34 +00:00
|
|
|
const int threads = 1;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (int i = 0; i < threads; i++) {
|
2017-01-27 04:00:43 +00:00
|
|
|
iothread_perform([=]() { test_universal_helper(i); });
|
2014-04-27 20:34:51 +00:00
|
|
|
}
|
|
|
|
iothread_drain_all();
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2021-05-10 19:26:30 +00:00
|
|
|
env_universal_t uvars;
|
2017-07-14 17:45:31 +00:00
|
|
|
callback_data_list_t callbacks;
|
2021-05-10 19:26:30 +00:00
|
|
|
uvars.initialize_at_path(callbacks, UVARS_TEST_PATH);
|
2016-05-01 01:37:19 +00:00
|
|
|
for (int i = 0; i < threads; i++) {
|
|
|
|
for (int j = 0; j < UVARS_PER_THREAD; j++) {
|
2014-04-27 20:34:51 +00:00
|
|
|
const wcstring key = format_string(L"key_%d_%d", i, j);
|
2017-08-28 07:25:41 +00:00
|
|
|
maybe_t<env_var_t> expected_val;
|
2016-05-01 01:37:19 +00:00
|
|
|
if (j == 0) {
|
2017-08-28 07:25:41 +00:00
|
|
|
expected_val = none();
|
2016-05-01 01:37:19 +00:00
|
|
|
} else {
|
2018-01-30 20:36:50 +00:00
|
|
|
expected_val = env_var_t(format_string(L"val_%d_%d", i, j), 0);
|
2014-04-30 00:03:00 +00:00
|
|
|
}
|
2017-08-28 07:25:41 +00:00
|
|
|
const maybe_t<env_var_t> var = uvars.get(key);
|
|
|
|
if (j == 0) assert(!expected_val);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (var != expected_val) {
|
2014-04-30 00:03:00 +00:00
|
|
|
const wchar_t *missing_desc = L"<missing>";
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"Wrong value for key %ls: expected %ls, got %ls\n", key.c_str(),
|
2017-08-28 07:25:41 +00:00
|
|
|
(expected_val ? expected_val->as_string().c_str() : missing_desc),
|
|
|
|
(var ? var->as_string().c_str() : missing_desc));
|
2014-04-27 20:34:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-05 22:38:32 +00:00
|
|
|
system_assert("rm -Rf test/fish_uvars_test/");
|
2014-04-27 20:34:51 +00:00
|
|
|
}
|
|
|
|
|
2018-10-20 22:55:46 +00:00
|
|
|
static void test_universal_output() {
|
|
|
|
say(L"Testing universal variable output");
|
2018-10-21 01:21:05 +00:00
|
|
|
|
|
|
|
const env_var_t::env_var_flags_t flag_export = env_var_t::flag_export;
|
|
|
|
const env_var_t::env_var_flags_t flag_pathvar = env_var_t::flag_pathvar;
|
|
|
|
|
2018-10-20 22:55:46 +00:00
|
|
|
var_table_t vars;
|
2023-04-18 22:19:10 +00:00
|
|
|
vars[L"varA"] = env_var_t(std::vector<wcstring>{L"ValA1", L"ValA2"}, 0);
|
|
|
|
vars[L"varB"] = env_var_t(std::vector<wcstring>{L"ValB1"}, flag_export);
|
|
|
|
vars[L"varC"] = env_var_t(std::vector<wcstring>{L"ValC1"}, 0);
|
|
|
|
vars[L"varD"] = env_var_t(std::vector<wcstring>{L"ValD1"}, flag_export | flag_pathvar);
|
|
|
|
vars[L"varE"] = env_var_t(std::vector<wcstring>{L"ValE1", L"ValE2"}, flag_pathvar);
|
2018-10-21 01:21:05 +00:00
|
|
|
|
2018-10-20 22:55:46 +00:00
|
|
|
std::string text = env_universal_t::serialize_with_vars(vars);
|
|
|
|
const char *expected =
|
|
|
|
"# This file contains fish universal variable definitions.\n"
|
2018-10-21 01:21:05 +00:00
|
|
|
"# VERSION: 3.0\n"
|
|
|
|
"SETUVAR varA:ValA1\\x1eValA2\n"
|
|
|
|
"SETUVAR --export varB:ValB1\n"
|
|
|
|
"SETUVAR varC:ValC1\n"
|
|
|
|
"SETUVAR --export --path varD:ValD1\n"
|
|
|
|
"SETUVAR --path varE:ValE1\\x1eValE2\n";
|
2018-10-20 22:55:46 +00:00
|
|
|
do_test(text == expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_universal_parsing() {
|
|
|
|
say(L"Testing universal variable parsing");
|
2018-10-21 00:20:49 +00:00
|
|
|
const char *input =
|
|
|
|
"# This file contains fish universal variable definitions.\n"
|
|
|
|
"# VERSION: 3.0\n"
|
|
|
|
"SETUVAR varA:ValA1\\x1eValA2\n"
|
|
|
|
"SETUVAR --export varB:ValB1\n"
|
|
|
|
"SETUVAR --nonsenseflag varC:ValC1\n"
|
|
|
|
"SETUVAR --export --path varD:ValD1\n"
|
|
|
|
"SETUVAR --path --path varE:ValE1\\x1eValE2\n";
|
|
|
|
|
|
|
|
const env_var_t::env_var_flags_t flag_export = env_var_t::flag_export;
|
|
|
|
const env_var_t::env_var_flags_t flag_pathvar = env_var_t::flag_pathvar;
|
|
|
|
|
|
|
|
var_table_t vars;
|
2023-04-18 22:19:10 +00:00
|
|
|
vars[L"varA"] = env_var_t(std::vector<wcstring>{L"ValA1", L"ValA2"}, 0);
|
|
|
|
vars[L"varB"] = env_var_t(std::vector<wcstring>{L"ValB1"}, flag_export);
|
|
|
|
vars[L"varC"] = env_var_t(std::vector<wcstring>{L"ValC1"}, 0);
|
|
|
|
vars[L"varD"] = env_var_t(std::vector<wcstring>{L"ValD1"}, flag_export | flag_pathvar);
|
|
|
|
vars[L"varE"] = env_var_t(std::vector<wcstring>{L"ValE1", L"ValE2"}, flag_pathvar);
|
2018-10-21 00:20:49 +00:00
|
|
|
|
|
|
|
var_table_t parsed_vars;
|
|
|
|
env_universal_t::populate_variables(input, &parsed_vars);
|
|
|
|
do_test(vars == parsed_vars);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_universal_parsing_legacy() {
|
|
|
|
say(L"Testing universal variable legacy parsing");
|
2018-10-20 22:55:46 +00:00
|
|
|
const char *input =
|
|
|
|
"# This file contains fish universal variable definitions.\n"
|
|
|
|
"SET varA:ValA1\\x1eValA2\n"
|
|
|
|
"SET_EXPORT varB:ValB1\n";
|
|
|
|
|
|
|
|
var_table_t vars;
|
2023-04-18 22:19:10 +00:00
|
|
|
vars[L"varA"] = env_var_t(std::vector<wcstring>{L"ValA1", L"ValA2"}, 0);
|
|
|
|
vars[L"varB"] = env_var_t(std::vector<wcstring>{L"ValB1"}, env_var_t::flag_export);
|
2018-10-20 22:55:46 +00:00
|
|
|
|
|
|
|
var_table_t parsed_vars;
|
|
|
|
env_universal_t::populate_variables(input, &parsed_vars);
|
|
|
|
do_test(vars == parsed_vars);
|
|
|
|
}
|
|
|
|
|
2014-06-16 19:25:33 +00:00
|
|
|
static bool callback_data_less_than(const callback_data_t &a, const callback_data_t &b) {
|
|
|
|
return a.key < b.key;
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_universal_callbacks() {
|
2014-06-16 19:25:33 +00:00
|
|
|
say(L"Testing universal callbacks");
|
2017-04-09 04:05:18 +00:00
|
|
|
if (system("mkdir -p test/fish_uvars_test/")) err(L"mkdir failed");
|
2017-08-28 09:51:34 +00:00
|
|
|
callback_data_list_t callbacks;
|
2021-05-10 19:26:30 +00:00
|
|
|
env_universal_t uvars1;
|
|
|
|
env_universal_t uvars2;
|
|
|
|
uvars1.initialize_at_path(callbacks, UVARS_TEST_PATH);
|
|
|
|
uvars2.initialize_at_path(callbacks, UVARS_TEST_PATH);
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2018-10-22 07:49:09 +00:00
|
|
|
env_var_t::env_var_flags_t noflags = 0;
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Put some variables into both.
|
2018-10-22 07:49:09 +00:00
|
|
|
uvars1.set(L"alpha", env_var_t{L"1", noflags});
|
|
|
|
uvars1.set(L"beta", env_var_t{L"1", noflags});
|
|
|
|
uvars1.set(L"delta", env_var_t{L"1", noflags});
|
|
|
|
uvars1.set(L"epsilon", env_var_t{L"1", noflags});
|
|
|
|
uvars1.set(L"lambda", env_var_t{L"1", noflags});
|
|
|
|
uvars1.set(L"kappa", env_var_t{L"1", noflags});
|
|
|
|
uvars1.set(L"omicron", env_var_t{L"1", noflags});
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2017-07-14 17:45:31 +00:00
|
|
|
uvars1.sync(callbacks);
|
|
|
|
uvars2.sync(callbacks);
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Change uvars1.
|
2018-10-22 07:49:09 +00:00
|
|
|
uvars1.set(L"alpha", env_var_t{L"2", noflags}); // changes value
|
|
|
|
uvars1.set(L"beta", env_var_t{L"1", env_var_t::flag_export}); // changes export
|
2019-05-05 10:09:25 +00:00
|
|
|
uvars1.remove(L"delta"); // erases value
|
|
|
|
uvars1.set(L"epsilon", env_var_t{L"1", noflags}); // changes nothing
|
2017-07-14 17:45:31 +00:00
|
|
|
uvars1.sync(callbacks);
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Change uvars2. It should treat its value as correct and ignore changes from uvars1.
|
2018-10-22 07:49:09 +00:00
|
|
|
uvars2.set(L"lambda", {L"1", noflags}); // same value
|
|
|
|
uvars2.set(L"kappa", {L"2", noflags}); // different value
|
2014-06-16 19:25:33 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Now see what uvars2 sees.
|
2017-07-14 17:45:31 +00:00
|
|
|
callbacks.clear();
|
|
|
|
uvars2.sync(callbacks);
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Sort them to get them in a predictable order.
|
2014-06-16 19:25:33 +00:00
|
|
|
std::sort(callbacks.begin(), callbacks.end(), callback_data_less_than);
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2017-08-28 09:51:34 +00:00
|
|
|
// Should see exactly three changes.
|
2014-06-16 19:25:33 +00:00
|
|
|
do_test(callbacks.size() == 3);
|
|
|
|
do_test(callbacks.at(0).key == L"alpha");
|
2021-11-15 01:39:52 +00:00
|
|
|
do_test(callbacks.at(0).val->as_string() == L"2");
|
2014-06-16 19:25:33 +00:00
|
|
|
do_test(callbacks.at(1).key == L"beta");
|
2021-11-15 01:39:52 +00:00
|
|
|
do_test(callbacks.at(1).val->as_string() == L"1");
|
2014-06-16 19:25:33 +00:00
|
|
|
do_test(callbacks.at(2).key == L"delta");
|
2018-10-20 20:16:14 +00:00
|
|
|
do_test(callbacks.at(2).val == none());
|
2020-07-05 22:38:32 +00:00
|
|
|
system_assert("rm -Rf test/fish_uvars_test/");
|
2014-06-16 19:25:33 +00:00
|
|
|
}
|
|
|
|
|
2018-10-20 21:38:49 +00:00
|
|
|
static void test_universal_formats() {
|
|
|
|
say(L"Testing universal format detection");
|
|
|
|
const struct {
|
|
|
|
const char *str;
|
|
|
|
uvar_format_t format;
|
|
|
|
} tests[] = {
|
|
|
|
{"# VERSION: 3.0", uvar_format_t::fish_3_0},
|
|
|
|
{"# version: 3.0", uvar_format_t::fish_2_x},
|
|
|
|
{"# blah blahVERSION: 3.0", uvar_format_t::fish_2_x},
|
|
|
|
{"stuff\n# blah blahVERSION: 3.0", uvar_format_t::fish_2_x},
|
|
|
|
{"# blah\n# VERSION: 3.0", uvar_format_t::fish_3_0},
|
|
|
|
{"# blah\n#VERSION: 3.0", uvar_format_t::fish_3_0},
|
|
|
|
{"# blah\n#VERSION:3.0", uvar_format_t::fish_3_0},
|
|
|
|
{"# blah\n#VERSION:3.1", uvar_format_t::future},
|
|
|
|
};
|
|
|
|
for (const auto &test : tests) {
|
|
|
|
uvar_format_t format = env_universal_t::format_for_contents(test.str);
|
|
|
|
do_test(format == test.format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-21 07:31:30 +00:00
|
|
|
static void test_universal_ok_to_save() {
|
|
|
|
// Ensure we don't try to save after reading from a newer fish.
|
|
|
|
say(L"Testing universal Ok to save");
|
|
|
|
if (system("mkdir -p test/fish_uvars_test/")) err(L"mkdir failed");
|
2021-02-17 18:55:21 +00:00
|
|
|
constexpr const char contents[] = "# VERSION: 99999.99\n";
|
2023-04-01 11:50:30 +00:00
|
|
|
FILE *fp = fopen(wcs2zstring(UVARS_TEST_PATH).c_str(), "w");
|
2018-10-21 07:31:30 +00:00
|
|
|
assert(fp && "Failed to open UVARS_TEST_PATH for writing");
|
2021-02-08 21:09:10 +00:00
|
|
|
fwrite(contents, const_strlen(contents), 1, fp);
|
2018-10-21 07:31:30 +00:00
|
|
|
fclose(fp);
|
|
|
|
|
|
|
|
file_id_t before_id = file_id_for_path(UVARS_TEST_PATH);
|
|
|
|
do_test(before_id != kInvalidFileID && "UVARS_TEST_PATH should be readable");
|
|
|
|
|
|
|
|
callback_data_list_t cbs;
|
2021-05-10 19:26:30 +00:00
|
|
|
env_universal_t uvars;
|
|
|
|
uvars.initialize_at_path(cbs, UVARS_TEST_PATH);
|
|
|
|
do_test(!uvars.is_ok_to_save() && "Should not be OK to save");
|
2018-10-21 07:31:30 +00:00
|
|
|
uvars.sync(cbs);
|
|
|
|
cbs.clear();
|
2021-05-10 19:26:30 +00:00
|
|
|
do_test(!uvars.is_ok_to_save() && "Should still not be OK to save");
|
2018-10-22 07:49:09 +00:00
|
|
|
uvars.set(L"SOMEVAR", env_var_t{wcstring{L"SOMEVALUE"}, 0});
|
2018-10-21 07:31:30 +00:00
|
|
|
uvars.sync(cbs);
|
|
|
|
|
|
|
|
// Ensure file is same.
|
|
|
|
file_id_t after_id = file_id_for_path(UVARS_TEST_PATH);
|
|
|
|
do_test(before_id == after_id && "UVARS_TEST_PATH should not have changed");
|
2020-07-05 22:38:32 +00:00
|
|
|
system_assert("rm -Rf test/fish_uvars_test/");
|
2018-10-21 07:31:30 +00:00
|
|
|
}
|
|
|
|
|
2017-01-22 00:56:45 +00:00
|
|
|
bool poll_notifier(const std::unique_ptr<universal_notifier_t> ¬e) {
|
2020-10-01 19:35:18 +00:00
|
|
|
if (note->poll()) return true;
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2020-10-01 19:35:18 +00:00
|
|
|
bool result = false;
|
2014-05-06 06:33:05 +00:00
|
|
|
int fd = note->notification_fd();
|
2023-01-14 22:56:24 +00:00
|
|
|
if (fd >= 0 && poll_fd_readable(fd)) {
|
2021-04-10 23:45:26 +00:00
|
|
|
result = note->notification_fd_became_readable(fd);
|
2014-04-30 22:50:03 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_notifiers_with_strategy(universal_notifier_t::notifier_strategy_t strategy) {
|
2014-04-30 00:03:00 +00:00
|
|
|
say(L"Testing universal notifiers with strategy %d", (int)strategy);
|
2021-05-03 18:51:00 +00:00
|
|
|
constexpr size_t notifier_count = 16;
|
|
|
|
std::unique_ptr<universal_notifier_t> notifiers[notifier_count];
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Populate array of notifiers.
|
2021-08-17 22:41:44 +00:00
|
|
|
for (auto ¬ifier : notifiers) {
|
|
|
|
notifier = universal_notifier_t::new_notifier_for_strategy(strategy, UVARS_TEST_PATH);
|
2014-04-29 21:14:50 +00:00
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Nobody should poll yet.
|
2021-08-17 22:41:44 +00:00
|
|
|
for (const auto ¬ifier : notifiers) {
|
|
|
|
if (poll_notifier(notifier)) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"Universal variable notifier polled true before any changes, with strategy %d",
|
|
|
|
(int)strategy);
|
2014-04-29 21:14:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tweak each notifier. Verify that others see it.
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t post_idx = 0; post_idx < notifier_count; post_idx++) {
|
2014-04-29 21:14:50 +00:00
|
|
|
notifiers[post_idx]->post_notification();
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2021-05-03 18:51:00 +00:00
|
|
|
if (strategy == universal_notifier_t::strategy_notifyd) {
|
|
|
|
// notifyd requires a round trip to the notifyd server, which means we have to wait a
|
|
|
|
// little bit to receive it. In practice 40 ms seems to be enough.
|
|
|
|
usleep(40000);
|
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < notifier_count; i++) {
|
2021-05-03 18:51:00 +00:00
|
|
|
bool polled = poll_notifier(notifiers[i]);
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// We aren't concerned with the one who posted. Poll from it (to drain it), and then
|
|
|
|
// skip it.
|
|
|
|
if (i == post_idx) {
|
2014-04-29 21:14:50 +00:00
|
|
|
continue;
|
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2021-05-03 18:51:00 +00:00
|
|
|
if (!polled) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"Universal variable notifier (%lu) %p polled failed to notice changes, with "
|
|
|
|
L"strategy %d",
|
2017-01-22 00:56:45 +00:00
|
|
|
i, notifiers[i].get(), (int)strategy);
|
2021-05-03 18:51:00 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// It should not poll again immediately.
|
|
|
|
if (poll_notifier(notifiers[i])) {
|
|
|
|
err(L"Universal variable notifier (%lu) %p polled twice in a row with strategy %d",
|
|
|
|
i, notifiers[i].get(), (int)strategy);
|
2014-05-06 06:33:05 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Named pipes have special cleanup requirements.
|
|
|
|
if (strategy == universal_notifier_t::strategy_named_pipe) {
|
|
|
|
usleep(1000000 / 10); // corresponds to NAMED_PIPE_FLASH_DURATION_USEC
|
|
|
|
// Have to clean up the posted one first, so that the others see the pipe become no
|
|
|
|
// longer readable.
|
2014-05-06 21:10:55 +00:00
|
|
|
poll_notifier(notifiers[post_idx]);
|
2021-08-17 22:41:44 +00:00
|
|
|
for (const auto ¬ifier : notifiers) {
|
|
|
|
poll_notifier(notifier);
|
2014-04-29 21:14:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Nobody should poll now.
|
2021-08-17 22:41:44 +00:00
|
|
|
for (const auto ¬ifier : notifiers) {
|
|
|
|
if (poll_notifier(notifier)) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"Universal variable notifier polled true after all changes, with strategy %d",
|
|
|
|
(int)strategy);
|
2014-04-29 21:14:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_universal_notifiers() {
|
2017-04-09 04:05:18 +00:00
|
|
|
if (system("mkdir -p test/fish_uvars_test/ && touch test/fish_uvars_test/varsfile.txt")) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"mkdir failed");
|
2016-12-26 04:53:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
auto strategy = universal_notifier_t::resolve_default_strategy();
|
|
|
|
test_notifiers_with_strategy(strategy);
|
2014-04-29 21:14:50 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
class history_tests_t {
|
|
|
|
public:
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_history();
|
|
|
|
static void test_history_merge();
|
2020-12-30 08:40:56 +00:00
|
|
|
static void test_history_path_detection();
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_history_formats();
|
2016-04-04 02:02:46 +00:00
|
|
|
// static void test_history_speed(void);
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_history_races();
|
2021-01-11 01:08:34 +00:00
|
|
|
static void test_history_races_pound_on_history(size_t item_count, size_t idx);
|
2012-02-16 08:24:27 +00:00
|
|
|
};
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static wcstring random_string() {
|
2020-06-06 22:19:19 +00:00
|
|
|
wcstring result;
|
2019-02-18 13:44:41 +00:00
|
|
|
size_t max = 1 + random() % 32;
|
2016-05-01 01:37:19 +00:00
|
|
|
while (max--) {
|
2019-02-18 13:44:41 +00:00
|
|
|
wchar_t c = 1 + random() % ESCAPE_TEST_CHAR;
|
2012-02-16 08:24:27 +00:00
|
|
|
result.push_back(c);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
void history_tests_t::test_history() {
|
2016-09-24 03:12:15 +00:00
|
|
|
history_search_t searcher;
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Testing history");
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
const std::vector<wcstring> items = {L"Gamma", L"beta", L"BetA", L"Beta", L"alpha",
|
2023-04-19 08:31:47 +00:00
|
|
|
L"AlphA", L"Alpha", L"alph", L"ALPH", L"ZZZ"};
|
2018-08-12 07:30:57 +00:00
|
|
|
const history_search_flags_t nocase = history_search_ignore_case;
|
|
|
|
|
|
|
|
// Populate a history.
|
2021-01-10 00:22:32 +00:00
|
|
|
std::shared_ptr<history_t> history = history_t::with_name(L"test_history");
|
|
|
|
history->clear();
|
2018-08-12 07:30:57 +00:00
|
|
|
for (const wcstring &s : items) {
|
2021-01-10 00:22:32 +00:00
|
|
|
history->add(s);
|
2018-08-12 07:30:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Helper to set expected items to those matching a predicate, in reverse order.
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> expected;
|
2018-08-12 07:30:57 +00:00
|
|
|
auto set_expected = [&](const std::function<bool(const wcstring &)> &filt) {
|
|
|
|
expected.clear();
|
|
|
|
for (const auto &s : items) {
|
|
|
|
if (filt(s)) expected.push_back(s);
|
|
|
|
}
|
|
|
|
std::reverse(expected.begin(), expected.end());
|
|
|
|
};
|
2016-09-24 03:12:15 +00:00
|
|
|
|
|
|
|
// Items matching "a", case-sensitive.
|
|
|
|
searcher = history_search_t(history, L"a");
|
2018-08-12 07:30:57 +00:00
|
|
|
set_expected([](const wcstring &s) { return s.find(L'a') != wcstring::npos; });
|
|
|
|
test_history_matches(searcher, expected, __LINE__);
|
2016-09-24 03:12:15 +00:00
|
|
|
|
2018-08-11 21:30:10 +00:00
|
|
|
// Items matching "alpha", case-insensitive.
|
2019-11-03 01:56:16 +00:00
|
|
|
searcher = history_search_t(history, L"AlPhA", history_search_type_t::contains, nocase);
|
2019-07-06 19:19:34 +00:00
|
|
|
set_expected([](const wcstring &s) { return wcstolower(s).find(L"alpha") != wcstring::npos; });
|
2018-08-12 07:30:57 +00:00
|
|
|
test_history_matches(searcher, expected, __LINE__);
|
2016-09-24 03:12:15 +00:00
|
|
|
|
|
|
|
// Items matching "et", case-sensitive.
|
|
|
|
searcher = history_search_t(history, L"et");
|
2018-08-12 07:30:57 +00:00
|
|
|
set_expected([](const wcstring &s) { return s.find(L"et") != wcstring::npos; });
|
|
|
|
test_history_matches(searcher, expected, __LINE__);
|
2016-09-24 03:12:15 +00:00
|
|
|
|
|
|
|
// Items starting with "be", case-sensitive.
|
2019-11-03 01:56:16 +00:00
|
|
|
searcher = history_search_t(history, L"be", history_search_type_t::prefix, 0);
|
2018-08-12 07:30:57 +00:00
|
|
|
set_expected([](const wcstring &s) { return string_prefixes_string(L"be", s); });
|
|
|
|
test_history_matches(searcher, expected, __LINE__);
|
2016-09-24 03:12:15 +00:00
|
|
|
|
|
|
|
// Items starting with "be", case-insensitive.
|
2019-11-03 01:56:16 +00:00
|
|
|
searcher = history_search_t(history, L"be", history_search_type_t::prefix, nocase);
|
2018-08-12 07:30:57 +00:00
|
|
|
set_expected(
|
|
|
|
[](const wcstring &s) { return string_prefixes_string_case_insensitive(L"be", s); });
|
|
|
|
test_history_matches(searcher, expected, __LINE__);
|
2016-09-24 03:12:15 +00:00
|
|
|
|
2018-08-11 21:30:10 +00:00
|
|
|
// Items exactly matching "alph", case-sensitive.
|
2019-11-03 01:56:16 +00:00
|
|
|
searcher = history_search_t(history, L"alph", history_search_type_t::exact, 0);
|
2018-08-12 07:30:57 +00:00
|
|
|
set_expected([](const wcstring &s) { return s == L"alph"; });
|
|
|
|
test_history_matches(searcher, expected, __LINE__);
|
2016-09-24 03:12:15 +00:00
|
|
|
|
2018-08-11 21:30:10 +00:00
|
|
|
// Items exactly matching "alph", case-insensitive.
|
2019-11-03 01:56:16 +00:00
|
|
|
searcher = history_search_t(history, L"alph", history_search_type_t::exact, nocase);
|
2019-07-06 19:19:34 +00:00
|
|
|
set_expected([](const wcstring &s) { return wcstolower(s) == L"alph"; });
|
2018-08-12 07:30:57 +00:00
|
|
|
test_history_matches(searcher, expected, __LINE__);
|
2016-09-24 03:12:15 +00:00
|
|
|
|
|
|
|
// Test item removal case-sensitive.
|
|
|
|
searcher = history_search_t(history, L"Alpha");
|
2018-08-12 07:30:57 +00:00
|
|
|
test_history_matches(searcher, {L"Alpha"}, __LINE__);
|
2021-01-10 00:22:32 +00:00
|
|
|
history->remove(L"Alpha");
|
2016-09-24 03:12:15 +00:00
|
|
|
searcher = history_search_t(history, L"Alpha");
|
2018-08-12 07:30:57 +00:00
|
|
|
test_history_matches(searcher, {}, __LINE__);
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Test history escaping and unescaping, yaml, etc.
|
2014-03-29 06:22:03 +00:00
|
|
|
history_item_list_t before, after;
|
2021-01-10 00:22:32 +00:00
|
|
|
history->clear();
|
2012-02-16 08:24:27 +00:00
|
|
|
size_t i, max = 100;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (i = 1; i <= max; i++) {
|
|
|
|
// Generate a value.
|
2012-03-07 08:54:01 +00:00
|
|
|
wcstring value = wcstring(L"test item ") + to_string(i);
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Maybe add some backslashes.
|
|
|
|
if (i % 3 == 0) value.append(L"(slashies \\\\\\ slashies)");
|
2012-02-16 08:24:27 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Generate some paths.
|
2012-11-18 10:23:22 +00:00
|
|
|
path_list_t paths;
|
2019-02-18 13:44:41 +00:00
|
|
|
size_t count = random() % 6;
|
2016-05-01 01:37:19 +00:00
|
|
|
while (count--) {
|
2012-07-25 05:31:31 +00:00
|
|
|
paths.push_back(random_string());
|
2012-02-16 08:24:27 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Record this item.
|
2021-08-17 22:57:52 +00:00
|
|
|
history_item_t item(value, time(nullptr));
|
2014-03-29 06:22:03 +00:00
|
|
|
item.required_paths = paths;
|
2012-02-16 08:24:27 +00:00
|
|
|
before.push_back(item);
|
2021-03-21 18:44:05 +00:00
|
|
|
history->add(std::move(item));
|
2012-02-16 08:24:27 +00:00
|
|
|
}
|
2021-01-10 00:22:32 +00:00
|
|
|
history->save();
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2020-01-05 20:38:49 +00:00
|
|
|
// Empty items should just be dropped (#6032).
|
2021-01-10 00:22:32 +00:00
|
|
|
history->add(L"");
|
|
|
|
do_test(!history->item_at_index(1).contents.empty());
|
2020-01-05 20:38:49 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Read items back in reverse order and ensure they're the same.
|
|
|
|
for (i = 100; i >= 1; i--) {
|
2021-01-10 00:22:32 +00:00
|
|
|
history_item_t item = history->item_at_index(i);
|
2016-05-01 01:37:19 +00:00
|
|
|
do_test(!item.empty());
|
2012-02-16 08:24:27 +00:00
|
|
|
after.push_back(item);
|
|
|
|
}
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(before.size() == after.size());
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < before.size(); i++) {
|
2012-02-16 08:24:27 +00:00
|
|
|
const history_item_t &bef = before.at(i), &aft = after.at(i);
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(bef.contents == aft.contents);
|
|
|
|
do_test(bef.creation_timestamp == aft.creation_timestamp);
|
|
|
|
do_test(bef.required_paths == aft.required_paths);
|
2012-02-16 08:24:27 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Clean up after our tests.
|
2021-01-10 00:22:32 +00:00
|
|
|
history->clear();
|
2012-04-17 03:26:50 +00:00
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
// Wait until the next second.
|
2018-02-19 02:33:04 +00:00
|
|
|
static void time_barrier() {
|
2021-08-17 22:57:52 +00:00
|
|
|
time_t start = time(nullptr);
|
2016-05-01 01:37:19 +00:00
|
|
|
do {
|
2012-04-17 03:26:50 +00:00
|
|
|
usleep(1000);
|
2021-08-17 22:57:52 +00:00
|
|
|
} while (time(nullptr) == start);
|
2012-04-17 03:26:50 +00:00
|
|
|
}
|
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
static std::vector<wcstring> generate_history_lines(size_t item_count, size_t idx) {
|
|
|
|
std::vector<wcstring> result;
|
2017-02-06 19:04:07 +00:00
|
|
|
result.reserve(item_count);
|
|
|
|
for (unsigned long i = 0; i < item_count; i++) {
|
2021-01-11 01:08:34 +00:00
|
|
|
result.push_back(format_string(L"%ld %lu", (unsigned long)idx, (unsigned long)i));
|
2012-12-03 07:38:38 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-01-11 01:08:34 +00:00
|
|
|
void history_tests_t::test_history_races_pound_on_history(size_t item_count, size_t idx) {
|
|
|
|
// Called in child thread to modify history.
|
2017-02-06 19:04:07 +00:00
|
|
|
history_t hist(L"race_test");
|
2023-04-18 22:19:10 +00:00
|
|
|
const std::vector<wcstring> hist_lines = generate_history_lines(item_count, idx);
|
2017-02-06 19:04:07 +00:00
|
|
|
for (const wcstring &line : hist_lines) {
|
|
|
|
hist.add(line);
|
|
|
|
hist.save();
|
2012-12-03 07:38:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
void history_tests_t::test_history_races() {
|
Make `test` a custom target again and add top-level test targets
Even though we are using CMake's ctest for testing, we still define our
own `make test` target rather than use its default for many reasons:
* CMake doesn't run tests in-proc or even add each tests as an
individual node in the ninja dependency tree, instead it just bundles
all tests into a target called `test` that always just shells out to
`ctest`, so there are no build-related benefits to not doing that
ourselves.
* CMake devs insist that it is appropriate for `make test` to never
depend on `make all`, i.e. running `make test` does not require any
of the binaries to be built before testing.
* The only way to have a test depend on a binary is to add a fake test
with a name like "build_fish" that executes CMake recursively to
build the `fish` target.
* It is not possible to set top-level CTest options/settings such as
CTEST_PARALLEL_LEVEL from within the CMake configuration file.
* Circling back to the point about individual tests not being actual
Makefile targets, CMake does not offer any way to execute a named
test via the `make`/`ninja`/whatever interface; the only way to
manually invoke test `foo` is to to manually run `ctest` and specify
a regex matching `foo` as an argument, e.g. `ctest -R ^foo$`... which
is really crazy.
With this patch, it is now possible to execute any single test by name,
by invoking the build directly, e.g. to run the `universal.fish` check:
`cmake --build build --target universal.fish` or
`ninja -C build universal.fish`. Unfortunately, this is not integrated
into the Makefile wrapper, so `make universal.fish` won't work (although
this can potentially be hacked around).
2021-08-08 23:31:50 +00:00
|
|
|
// This always fails under WSL
|
|
|
|
if (is_windows_subsystem_for_linux()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-05 17:36:26 +00:00
|
|
|
// This fails too often on Github Actions,
|
|
|
|
// leading to a bunch of spurious test failures on unrelated PRs.
|
|
|
|
// For now it's better to disable it.
|
|
|
|
// TODO: Figure out *why* it does that and fix it.
|
|
|
|
if (getenv("CI")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-12-03 07:38:38 +00:00
|
|
|
say(L"Testing history race conditions");
|
2012-12-03 10:25:08 +00:00
|
|
|
|
2017-02-06 19:04:07 +00:00
|
|
|
// Test concurrent history writing.
|
|
|
|
// How many concurrent writers we have
|
2017-09-12 05:34:59 +00:00
|
|
|
constexpr size_t RACE_COUNT = 4;
|
2017-02-06 19:04:07 +00:00
|
|
|
|
|
|
|
// How many items each writer makes
|
|
|
|
constexpr size_t ITEM_COUNT = 256;
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Ensure history is clear.
|
2017-02-06 19:04:07 +00:00
|
|
|
history_t(L"race_test").clear();
|
2012-12-03 10:25:08 +00:00
|
|
|
|
2021-01-11 01:08:34 +00:00
|
|
|
// hist.chaos_mode = true;
|
|
|
|
|
|
|
|
std::thread children[RACE_COUNT];
|
|
|
|
for (size_t i = 0; i < RACE_COUNT; i++) {
|
|
|
|
children[i] = std::thread([=] { test_history_races_pound_on_history(ITEM_COUNT, i); });
|
2012-12-03 07:38:38 +00:00
|
|
|
}
|
2012-12-03 10:25:08 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Wait for all children.
|
2021-01-11 01:08:34 +00:00
|
|
|
for (std::thread &child : children) {
|
|
|
|
child.join();
|
2012-12-03 07:38:38 +00:00
|
|
|
}
|
2012-12-03 10:25:08 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Compute the expected lines.
|
2023-04-18 22:19:10 +00:00
|
|
|
std::array<std::vector<wcstring>, RACE_COUNT> expected_lines;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < RACE_COUNT; i++) {
|
2021-01-11 01:08:34 +00:00
|
|
|
expected_lines[i] = generate_history_lines(ITEM_COUNT, i);
|
2012-12-03 07:38:38 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Ensure we consider the lines that have been outputted as part of our history.
|
2012-12-03 07:38:38 +00:00
|
|
|
time_barrier();
|
2012-12-03 10:25:08 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Ensure that we got sane, sorted results.
|
2017-02-06 19:04:07 +00:00
|
|
|
history_t hist(L"race_test");
|
|
|
|
hist.chaos_mode = !true;
|
|
|
|
|
|
|
|
// History is enumerated from most recent to least
|
|
|
|
// Every item should be the last item in some array
|
2012-12-03 07:38:38 +00:00
|
|
|
size_t hist_idx;
|
2017-02-08 05:52:35 +00:00
|
|
|
for (hist_idx = 1;; hist_idx++) {
|
2017-02-06 19:04:07 +00:00
|
|
|
history_item_t item = hist.item_at_index(hist_idx);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (item.empty()) break;
|
2012-12-03 10:25:08 +00:00
|
|
|
|
2017-02-06 19:04:07 +00:00
|
|
|
bool found = false;
|
2023-04-18 22:19:10 +00:00
|
|
|
for (std::vector<wcstring> &list : expected_lines) {
|
2017-02-06 19:04:07 +00:00
|
|
|
auto iter = std::find(list.begin(), list.end(), item.contents);
|
|
|
|
if (iter != list.end()) {
|
|
|
|
found = true;
|
|
|
|
|
|
|
|
// Remove everything from this item on
|
|
|
|
auto cursor = list.end();
|
|
|
|
if (cursor + 1 != list.end()) {
|
|
|
|
while (--cursor != iter) {
|
|
|
|
err(L"Item dropped from history: %ls", cursor->c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
list.erase(iter, list.end());
|
2012-12-03 07:38:38 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-02-08 05:52:35 +00:00
|
|
|
if (!found) {
|
2017-02-06 19:04:07 +00:00
|
|
|
err(L"Line '%ls' found in history, but not found in some array", item.str().c_str());
|
2023-04-18 22:19:10 +00:00
|
|
|
for (std::vector<wcstring> &list : expected_lines) {
|
2017-02-08 05:52:35 +00:00
|
|
|
if (!list.empty()) {
|
2017-02-06 19:04:07 +00:00
|
|
|
fprintf(stderr, "\tRemaining: %ls\n", list.back().c_str());
|
|
|
|
}
|
|
|
|
}
|
2012-12-03 09:53:52 +00:00
|
|
|
}
|
2012-12-03 07:38:38 +00:00
|
|
|
}
|
2012-12-03 10:25:08 +00:00
|
|
|
|
2017-02-06 19:04:07 +00:00
|
|
|
// +1 to account for history's 1-based offset
|
|
|
|
size_t expected_idx = RACE_COUNT * ITEM_COUNT + 1;
|
|
|
|
if (hist_idx != expected_idx) {
|
|
|
|
err(L"Expected %lu items, but instead got %lu items", expected_idx, hist_idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
// See if anything is left in the arrays
|
2023-04-18 22:19:10 +00:00
|
|
|
for (const std::vector<wcstring> &list : expected_lines) {
|
2017-02-06 19:04:07 +00:00
|
|
|
for (const wcstring &str : list) {
|
|
|
|
err(L"Line '%ls' still left in the array", str.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
hist.clear();
|
2012-12-03 07:38:38 +00:00
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
void history_tests_t::test_history_merge() {
|
2016-05-01 01:37:19 +00:00
|
|
|
// In a single fish process, only one history is allowed to exist with the given name But it's
|
|
|
|
// common to have multiple history instances with the same name active in different processes,
|
|
|
|
// e.g. when you have multiple shells open. We try to get that right and merge all their history
|
|
|
|
// together. Test that case.
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Testing history merge");
|
2012-04-17 03:26:50 +00:00
|
|
|
const size_t count = 3;
|
|
|
|
const wcstring name = L"merge_test";
|
2021-01-10 00:22:32 +00:00
|
|
|
std::shared_ptr<history_t> hists[count] = {std::make_shared<history_t>(name),
|
|
|
|
std::make_shared<history_t>(name),
|
|
|
|
std::make_shared<history_t>(name)};
|
2014-07-25 17:08:21 +00:00
|
|
|
const wcstring texts[count] = {L"History 1", L"History 2", L"History 3"};
|
|
|
|
const wcstring alt_texts[count] = {L"History Alt 1", L"History Alt 2", L"History Alt 3"};
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Make sure history is clear.
|
2020-06-30 22:29:06 +00:00
|
|
|
for (auto &hist : hists) {
|
|
|
|
hist->clear();
|
2012-04-17 03:26:50 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Make sure we don't add an item in the same second as we created the history.
|
2012-04-17 03:26:50 +00:00
|
|
|
time_barrier();
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Add a different item to each.
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
2012-04-17 03:26:50 +00:00
|
|
|
hists[i]->add(texts[i]);
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Save them.
|
2020-06-30 22:29:06 +00:00
|
|
|
for (auto &hist : hists) {
|
|
|
|
hist->save();
|
2012-04-17 03:26:50 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Make sure each history contains what it ought to, but they have not leaked into each other.
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
for (size_t j = 0; j < count; j++) {
|
2012-04-17 03:26:50 +00:00
|
|
|
bool does_contain = history_contains(hists[i], texts[j]);
|
|
|
|
bool should_contain = (i == j);
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(should_contain == does_contain);
|
2012-04-17 03:26:50 +00:00
|
|
|
}
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Make a new history. It should contain everything. The time_barrier() is so that the timestamp
|
|
|
|
// is newer, since we only pick up items whose timestamp is before the birth stamp.
|
2012-04-17 03:26:50 +00:00
|
|
|
time_barrier();
|
2021-01-10 00:22:32 +00:00
|
|
|
std::shared_ptr<history_t> everything = std::make_shared<history_t>(name);
|
2020-06-30 22:29:06 +00:00
|
|
|
for (const auto &text : texts) {
|
|
|
|
do_test(history_contains(everything, text));
|
2012-04-17 03:26:50 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Tell all histories to merge. Now everybody should have everything.
|
2020-06-30 22:29:06 +00:00
|
|
|
for (auto &hist : hists) {
|
|
|
|
hist->incorporate_external_changes();
|
2014-07-25 17:08:21 +00:00
|
|
|
}
|
2016-06-24 05:02:39 +00:00
|
|
|
|
|
|
|
// Everyone should also have items in the same order (#2312)
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> hist_vals1;
|
2017-08-25 01:59:50 +00:00
|
|
|
hists[0]->get_history(hist_vals1);
|
2020-06-30 22:29:06 +00:00
|
|
|
for (const auto &hist : hists) {
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> hist_vals2;
|
2020-06-30 22:29:06 +00:00
|
|
|
hist->get_history(hist_vals2);
|
2017-08-25 01:59:50 +00:00
|
|
|
do_test(hist_vals1 == hist_vals2);
|
2016-06-24 05:02:39 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Add some more per-history items.
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
2014-07-25 17:08:21 +00:00
|
|
|
hists[i]->add(alt_texts[i]);
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
// Everybody should have old items, but only one history should have each new item.
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
for (size_t j = 0; j < count; j++) {
|
|
|
|
// Old item.
|
2014-07-25 17:08:21 +00:00
|
|
|
do_test(history_contains(hists[i], texts[j]));
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// New item.
|
2014-07-25 17:08:21 +00:00
|
|
|
bool does_contain = history_contains(hists[i], alt_texts[j]);
|
|
|
|
bool should_contain = (i == j);
|
|
|
|
do_test(should_contain == does_contain);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-20 06:39:58 +00:00
|
|
|
// Make sure incorporate_external_changes doesn't drop items! (#3496)
|
2017-01-22 01:01:38 +00:00
|
|
|
history_t *const writer = hists[0].get();
|
|
|
|
history_t *const reader = hists[1].get();
|
2016-11-23 04:24:03 +00:00
|
|
|
const wcstring more_texts[] = {L"Item_#3496_1", L"Item_#3496_2", L"Item_#3496_3",
|
|
|
|
L"Item_#3496_4", L"Item_#3496_5", L"Item_#3496_6"};
|
|
|
|
for (size_t i = 0; i < sizeof more_texts / sizeof *more_texts; i++) {
|
2016-11-20 06:39:58 +00:00
|
|
|
// time_barrier because merging will ignore items that may be newer
|
|
|
|
if (i > 0) time_barrier();
|
|
|
|
writer->add(more_texts[i]);
|
|
|
|
writer->incorporate_external_changes();
|
|
|
|
reader->incorporate_external_changes();
|
2016-11-23 04:24:03 +00:00
|
|
|
for (size_t j = 0; j < i; j++) {
|
2016-11-20 06:39:58 +00:00
|
|
|
do_test(history_contains(reader, more_texts[j]));
|
|
|
|
}
|
|
|
|
}
|
2012-04-17 03:26:50 +00:00
|
|
|
everything->clear();
|
2012-02-06 00:42:24 +00:00
|
|
|
}
|
|
|
|
|
2020-12-30 08:40:56 +00:00
|
|
|
void history_tests_t::test_history_path_detection() {
|
|
|
|
// Regression test for #7582.
|
|
|
|
say(L"Testing history path detection");
|
|
|
|
char tmpdirbuff[] = "/tmp/fish_test_history.XXXXXX";
|
|
|
|
wcstring tmpdir = str2wcstring(mkdtemp(tmpdirbuff));
|
2021-01-31 19:32:10 +00:00
|
|
|
if (!string_suffixes_string(L"/", tmpdir)) {
|
2020-12-30 08:40:56 +00:00
|
|
|
tmpdir.push_back(L'/');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Place one valid file in the directory.
|
|
|
|
wcstring filename = L"testfile";
|
2023-04-01 11:50:30 +00:00
|
|
|
std::string path = wcs2zstring(tmpdir + filename);
|
2020-12-30 08:40:56 +00:00
|
|
|
FILE *f = fopen(path.c_str(), "w");
|
|
|
|
if (!f) {
|
|
|
|
err(L"Failed to open test file from history path detection");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
|
2020-12-31 21:30:58 +00:00
|
|
|
std::shared_ptr<test_environment_t> vars = std::make_shared<test_environment_t>();
|
|
|
|
vars->vars[L"PWD"] = tmpdir;
|
|
|
|
vars->vars[L"HOME"] = tmpdir;
|
2020-12-30 08:40:56 +00:00
|
|
|
|
2021-01-10 00:22:32 +00:00
|
|
|
std::shared_ptr<history_t> history = history_t::with_name(L"path_detection");
|
|
|
|
history_t::add_pending_with_file_detection(history, L"cmd0 not/a/valid/path", vars);
|
|
|
|
history_t::add_pending_with_file_detection(history, L"cmd1 " + filename, vars);
|
|
|
|
history_t::add_pending_with_file_detection(history, L"cmd2 " + tmpdir + L"/" + filename, vars);
|
|
|
|
history_t::add_pending_with_file_detection(history, L"cmd3 $HOME/" + filename, vars);
|
|
|
|
history_t::add_pending_with_file_detection(history, L"cmd4 $HOME/notafile", vars);
|
|
|
|
history_t::add_pending_with_file_detection(history, L"cmd5 ~/" + filename, vars);
|
|
|
|
history_t::add_pending_with_file_detection(history, L"cmd6 ~/notafile", vars);
|
|
|
|
history_t::add_pending_with_file_detection(history, L"cmd7 ~/*f*", vars);
|
|
|
|
history_t::add_pending_with_file_detection(history, L"cmd8 ~/*zzz*", vars);
|
|
|
|
history->resolve_pending();
|
2020-12-30 08:40:56 +00:00
|
|
|
|
2020-12-31 21:30:58 +00:00
|
|
|
constexpr size_t hist_size = 9;
|
2021-01-10 00:22:32 +00:00
|
|
|
if (history->size() != hist_size) {
|
|
|
|
err(L"history has wrong size: %lu but expected %lu", (unsigned long)history->size(),
|
|
|
|
(unsigned long)hist_size);
|
|
|
|
history->clear();
|
2020-12-30 08:40:56 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expected sets of paths.
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> expected[hist_size] = {
|
2021-01-09 23:02:21 +00:00
|
|
|
{}, // cmd0
|
|
|
|
{filename}, // cmd1
|
|
|
|
{tmpdir + L"/" + filename}, // cmd2
|
|
|
|
{L"$HOME/" + filename}, // cmd3
|
|
|
|
{}, // cmd4
|
|
|
|
{L"~/" + filename}, // cmd5
|
|
|
|
{}, // cmd6
|
|
|
|
{}, // cmd7 - we do not expand globs
|
|
|
|
{}, // cmd8
|
2020-12-30 08:40:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
size_t lap;
|
|
|
|
const size_t maxlap = 128;
|
|
|
|
for (lap = 0; lap < maxlap; lap++) {
|
|
|
|
int failures = 0;
|
|
|
|
bool last = (lap + 1 == maxlap);
|
|
|
|
for (size_t i = 1; i <= hist_size; i++) {
|
2021-01-10 00:22:32 +00:00
|
|
|
if (history->item_at_index(i).required_paths != expected[hist_size - i]) {
|
2020-12-30 08:40:56 +00:00
|
|
|
failures += 1;
|
|
|
|
if (last) {
|
|
|
|
err(L"Wrong detected paths for item %lu", (unsigned long)i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (failures == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// The file detection takes a little time since it occurs in the background.
|
|
|
|
// Loop until the test passes.
|
|
|
|
usleep(1E6 / 500); // 1 msec
|
|
|
|
}
|
2021-01-31 19:32:10 +00:00
|
|
|
// fprintf(stderr, "History saving took %lu laps\n", (unsigned long)lap);
|
2021-01-10 00:22:32 +00:00
|
|
|
history->clear();
|
2020-12-30 08:40:56 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static bool install_sample_history(const wchar_t *name) {
|
2015-09-18 04:56:07 +00:00
|
|
|
wcstring path;
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!path_get_data(path)) {
|
2015-09-18 04:56:07 +00:00
|
|
|
err(L"Failed to get data directory");
|
|
|
|
return false;
|
|
|
|
}
|
2012-06-15 23:22:37 +00:00
|
|
|
char command[512];
|
2015-09-18 04:56:07 +00:00
|
|
|
snprintf(command, sizeof command, "cp tests/%ls %ls/%ls_history", name, path.c_str(), name);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (system(command)) {
|
2012-06-15 23:22:37 +00:00
|
|
|
err(L"Failed to copy sample history");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Indicates whether the history is equal to the given null-terminated array of strings.
|
2021-01-10 00:22:32 +00:00
|
|
|
static bool history_equals(const shared_ptr<history_t> &hist, const wchar_t *const *strings) {
|
2016-05-01 01:37:19 +00:00
|
|
|
// Count our expected items.
|
2012-06-15 23:22:37 +00:00
|
|
|
size_t expected_count = 0;
|
2016-05-01 01:37:19 +00:00
|
|
|
while (strings[expected_count]) {
|
2012-06-15 23:22:37 +00:00
|
|
|
expected_count++;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Ensure the contents are the same.
|
2012-06-15 23:22:37 +00:00
|
|
|
size_t history_idx = 1;
|
|
|
|
size_t array_idx = 0;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (;;) {
|
2012-06-15 23:22:37 +00:00
|
|
|
const wchar_t *expected = strings[array_idx];
|
2021-01-10 00:22:32 +00:00
|
|
|
history_item_t item = hist->item_at_index(history_idx);
|
2021-08-17 22:57:52 +00:00
|
|
|
if (expected == nullptr) {
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!item.empty()) {
|
2021-01-31 19:32:10 +00:00
|
|
|
err(L"Expected empty item at history index %lu, instead found: %ls", history_idx,
|
|
|
|
item.str().c_str());
|
2012-06-15 23:22:37 +00:00
|
|
|
}
|
|
|
|
break;
|
2016-05-01 01:37:19 +00:00
|
|
|
} else {
|
|
|
|
if (item.str() != expected) {
|
|
|
|
err(L"Expected '%ls', found '%ls' at index %lu", expected, item.str().c_str(),
|
|
|
|
history_idx);
|
2012-06-15 23:22:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
history_idx++;
|
|
|
|
array_idx++;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2012-06-15 23:22:37 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
void history_tests_t::test_history_formats() {
|
2012-06-15 23:22:37 +00:00
|
|
|
const wchar_t *name;
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Test inferring and reading legacy and bash history formats.
|
2012-06-15 23:22:37 +00:00
|
|
|
name = L"history_sample_fish_1_x";
|
|
|
|
say(L"Testing %ls", name);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!install_sample_history(name)) {
|
2012-06-15 23:22:37 +00:00
|
|
|
err(L"Couldn't open file tests/%ls", name);
|
2016-05-01 01:37:19 +00:00
|
|
|
} else {
|
|
|
|
// Note: This is backwards from what appears in the file.
|
|
|
|
const wchar_t *const expected[] = {
|
2021-08-17 22:57:52 +00:00
|
|
|
L"#def", L"echo #abc", L"function yay\necho hi\nend", L"cd foobar", L"ls /", nullptr};
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2021-01-10 00:22:32 +00:00
|
|
|
auto test_history = history_t::with_name(name);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!history_equals(test_history, expected)) {
|
2012-06-15 23:22:37 +00:00
|
|
|
err(L"test_history_formats failed for %ls\n", name);
|
|
|
|
}
|
2021-01-10 00:22:32 +00:00
|
|
|
test_history->clear();
|
2012-06-15 23:22:37 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2012-06-15 23:22:37 +00:00
|
|
|
name = L"history_sample_fish_2_0";
|
|
|
|
say(L"Testing %ls", name);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!install_sample_history(name)) {
|
2012-06-15 23:22:37 +00:00
|
|
|
err(L"Couldn't open file tests/%ls", name);
|
2016-05-01 01:37:19 +00:00
|
|
|
} else {
|
|
|
|
const wchar_t *const expected[] = {L"echo this has\\\nbackslashes",
|
2021-08-17 22:57:52 +00:00
|
|
|
L"function foo\necho bar\nend", L"echo alpha", nullptr};
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2021-01-10 00:22:32 +00:00
|
|
|
auto test_history = history_t::with_name(name);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!history_equals(test_history, expected)) {
|
2012-06-15 23:22:37 +00:00
|
|
|
err(L"test_history_formats failed for %ls\n", name);
|
|
|
|
}
|
2021-01-10 00:22:32 +00:00
|
|
|
test_history->clear();
|
2012-06-15 23:22:37 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2012-06-15 23:22:37 +00:00
|
|
|
say(L"Testing bash import");
|
|
|
|
FILE *f = fopen("tests/history_sample_bash", "r");
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!f) {
|
2012-06-15 23:22:37 +00:00
|
|
|
err(L"Couldn't open file tests/history_sample_bash");
|
2016-05-01 01:37:19 +00:00
|
|
|
} else {
|
2017-02-09 04:50:57 +00:00
|
|
|
// The results are in the reverse order that they appear in the bash history file.
|
2018-10-01 21:48:52 +00:00
|
|
|
// We don't expect whitespace to be elided (#4908: except for leading/trailing whitespace)
|
2021-04-21 20:31:58 +00:00
|
|
|
const wchar_t *expected[] = {L"EOF",
|
|
|
|
L"sleep 123",
|
2021-07-02 21:11:03 +00:00
|
|
|
L"posix_cmd_sub $(is supported but only splits on newlines)",
|
|
|
|
L"posix_cmd_sub \"$(is supported)\"",
|
2021-04-21 20:31:58 +00:00
|
|
|
L"a && echo valid construct",
|
|
|
|
L"final line",
|
|
|
|
L"echo supsup",
|
|
|
|
L"export XVAR='exported'",
|
|
|
|
L"history --help",
|
|
|
|
L"echo foo",
|
2021-08-17 22:57:52 +00:00
|
|
|
nullptr};
|
2021-01-10 00:22:32 +00:00
|
|
|
auto test_history = history_t::with_name(L"bash_import");
|
|
|
|
test_history->populate_from_bash(f);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!history_equals(test_history, expected)) {
|
2012-06-15 23:22:37 +00:00
|
|
|
err(L"test_history_formats failed for bash import\n");
|
|
|
|
}
|
2021-01-10 00:22:32 +00:00
|
|
|
test_history->clear();
|
2012-06-15 23:22:37 +00:00
|
|
|
fclose(f);
|
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2014-07-29 21:41:21 +00:00
|
|
|
name = L"history_sample_corrupt1";
|
|
|
|
say(L"Testing %ls", name);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!install_sample_history(name)) {
|
2014-07-29 21:41:21 +00:00
|
|
|
err(L"Couldn't open file tests/%ls", name);
|
2016-05-01 01:37:19 +00:00
|
|
|
} else {
|
|
|
|
// We simply invoke get_string_representation. If we don't die, the test is a success.
|
2021-01-10 00:22:32 +00:00
|
|
|
auto test_history = history_t::with_name(name);
|
2016-05-01 01:37:19 +00:00
|
|
|
const wchar_t *expected[] = {L"no_newline_at_end_of_file", L"corrupt_prefix",
|
2021-08-17 22:57:52 +00:00
|
|
|
L"this_command_is_ok", nullptr};
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!history_equals(test_history, expected)) {
|
2014-07-29 21:41:21 +00:00
|
|
|
err(L"test_history_formats failed for %ls\n", name);
|
|
|
|
}
|
2021-01-10 00:22:32 +00:00
|
|
|
test_history->clear();
|
2014-07-29 21:41:21 +00:00
|
|
|
}
|
2012-06-15 23:22:37 +00:00
|
|
|
}
|
|
|
|
|
2016-04-04 02:02:46 +00:00
|
|
|
#if 0
|
|
|
|
// This test isn't run at this time. It was added by commit b9283d48 but not actually enabled.
|
2012-12-03 10:25:08 +00:00
|
|
|
void history_tests_t::test_history_speed(void)
|
|
|
|
{
|
2013-04-27 22:21:14 +00:00
|
|
|
say(L"Testing history speed (pid is %d)", getpid());
|
2017-01-22 01:01:38 +00:00
|
|
|
std::unique_ptr<history_t> hist = make_unique<history_t>(L"speed_test");
|
2012-12-03 10:25:08 +00:00
|
|
|
wcstring item = L"History Speed Test - X";
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Test for 10 seconds.
|
2012-12-03 10:25:08 +00:00
|
|
|
double start = timef();
|
2013-04-27 22:21:14 +00:00
|
|
|
double end = start + 10;
|
2012-12-03 10:25:08 +00:00
|
|
|
double stop = 0;
|
|
|
|
size_t count = 0;
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
item[item.size() - 1] = L'0' + (count % 10);
|
|
|
|
hist->add(item);
|
|
|
|
count++;
|
|
|
|
|
|
|
|
stop = timef();
|
|
|
|
if (stop >= end)
|
|
|
|
break;
|
|
|
|
}
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stdout, L"%lu items - %.2f msec per item\n", (unsigned long)count,
|
2017-05-05 05:42:42 +00:00
|
|
|
(stop - start) * 1E6 / count);
|
2012-12-03 10:25:08 +00:00
|
|
|
hist->clear();
|
|
|
|
}
|
2016-04-04 02:02:46 +00:00
|
|
|
#endif
|
2012-12-03 10:25:08 +00:00
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_new_parser_correctness() {
|
2018-07-21 22:31:14 +00:00
|
|
|
say(L"Testing parser correctness");
|
2016-05-01 01:37:19 +00:00
|
|
|
const struct parser_test_t {
|
2013-08-08 22:06:46 +00:00
|
|
|
const wchar_t *src;
|
|
|
|
bool ok;
|
2016-05-01 01:37:19 +00:00
|
|
|
} parser_tests[] = {
|
2013-08-08 22:06:46 +00:00
|
|
|
{L"; ; ; ", true},
|
|
|
|
{L"if ; end", false},
|
|
|
|
{L"if true ; end", true},
|
|
|
|
{L"if true; end ; end", false},
|
|
|
|
{L"if end; end ; end", false},
|
2013-08-11 07:35:00 +00:00
|
|
|
{L"if end", false},
|
|
|
|
{L"end", false},
|
|
|
|
{L"for i i", false},
|
2015-03-12 10:54:47 +00:00
|
|
|
{L"for i in a b c ; end", true},
|
|
|
|
{L"begin end", true},
|
|
|
|
{L"begin; end", true},
|
|
|
|
{L"begin if true; end; end;", true},
|
|
|
|
{L"begin if true ; echo hi ; end; end", true},
|
2018-03-01 21:39:39 +00:00
|
|
|
{L"true && false || false", true},
|
|
|
|
{L"true || false; and true", true},
|
|
|
|
{L"true || ||", false},
|
|
|
|
{L"|| true", false},
|
2020-08-04 19:39:37 +00:00
|
|
|
{L"true || \n\n false", true},
|
2013-08-08 22:06:46 +00:00
|
|
|
};
|
2013-08-11 07:35:00 +00:00
|
|
|
|
2020-06-30 22:29:06 +00:00
|
|
|
for (const auto &test : parser_tests) {
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
auto ast = ast_parse(test.src);
|
|
|
|
bool success = !ast->errored();
|
2020-06-30 22:29:06 +00:00
|
|
|
if (success && !test.ok) {
|
|
|
|
err(L"\"%ls\" should NOT have parsed, but did", test.src);
|
|
|
|
} else if (!success && test.ok) {
|
|
|
|
err(L"\"%ls\" should have parsed, but failed", test.src);
|
2013-08-08 22:06:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
say(L"Parse tests complete");
|
2013-08-11 07:35:00 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Given that we have an array of 'fuzz_count' strings, we wish to enumerate all permutations of
|
|
|
|
// 'len' values. We do this by incrementing an integer, interpreting it as "base fuzz_count".
|
|
|
|
static inline bool string_for_permutation(const wcstring *fuzzes, size_t fuzz_count, size_t len,
|
|
|
|
size_t permutation, wcstring *out_str) {
|
2014-01-13 06:39:12 +00:00
|
|
|
out_str->clear();
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2014-01-13 06:39:12 +00:00
|
|
|
size_t remaining_permutation = permutation;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < len; i++) {
|
2014-01-13 06:39:12 +00:00
|
|
|
size_t idx = remaining_permutation % fuzz_count;
|
|
|
|
remaining_permutation /= fuzz_count;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2014-01-13 06:39:12 +00:00
|
|
|
out_str->append(fuzzes[idx]);
|
|
|
|
out_str->push_back(L' ');
|
2013-08-11 07:35:00 +00:00
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
// Return false if we wrapped.
|
2014-01-13 06:39:12 +00:00
|
|
|
return remaining_permutation == 0;
|
2013-08-11 07:35:00 +00:00
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_new_parser_fuzzing() {
|
2020-07-02 07:12:28 +00:00
|
|
|
say(L"Fuzzing parser");
|
2016-05-01 01:37:19 +00:00
|
|
|
const wcstring fuzzes[] = {
|
|
|
|
L"if", L"else", L"for", L"in", L"while", L"begin", L"function",
|
|
|
|
L"switch", L"case", L"end", L"and", L"or", L"not", L"command",
|
|
|
|
L"builtin", L"foo", L"|", L"^", L"&", L";",
|
2014-01-13 06:39:12 +00:00
|
|
|
};
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Generate a list of strings of all keyword / token combinations.
|
2014-01-13 06:39:12 +00:00
|
|
|
wcstring src;
|
|
|
|
src.reserve(128);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2023-02-05 08:35:06 +00:00
|
|
|
auto errors = new_parse_error_list();
|
2014-01-13 06:39:12 +00:00
|
|
|
|
2013-08-11 07:35:00 +00:00
|
|
|
double start = timef();
|
2014-01-13 06:39:12 +00:00
|
|
|
bool log_it = true;
|
2014-01-15 14:36:50 +00:00
|
|
|
unsigned long max_len = 5;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (unsigned long len = 0; len < max_len; len++) {
|
2019-03-12 21:06:01 +00:00
|
|
|
if (log_it) std::fwprintf(stderr, L"%lu / %lu...", len, max_len);
|
2013-08-11 07:35:00 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// We wish to look at all permutations of 4 elements of 'fuzzes' (with replacement).
|
|
|
|
// Construct an int and keep incrementing it.
|
2014-01-15 14:36:50 +00:00
|
|
|
unsigned long permutation = 0;
|
2016-05-01 01:37:19 +00:00
|
|
|
while (string_for_permutation(fuzzes, sizeof fuzzes / sizeof *fuzzes, len, permutation++,
|
|
|
|
&src)) {
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
ast_parse(src);
|
2013-08-11 07:35:00 +00:00
|
|
|
}
|
2019-03-12 21:06:01 +00:00
|
|
|
if (log_it) std::fwprintf(stderr, L"done (%lu)\n", permutation);
|
2013-08-11 07:35:00 +00:00
|
|
|
}
|
|
|
|
double end = timef();
|
2021-11-17 15:39:13 +00:00
|
|
|
if (log_it) say(L"All fuzzed in %.2f seconds!", end - start);
|
2013-08-08 22:06:46 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Parse a statement, returning the command, args (joined by spaces), and the decoration. Returns
|
|
|
|
// true if successful.
|
|
|
|
static bool test_1_parse_ll2(const wcstring &src, wcstring *out_cmd, wcstring *out_joined_args,
|
2023-02-05 08:35:06 +00:00
|
|
|
statement_decoration_t *out_deco) {
|
2020-07-02 07:12:28 +00:00
|
|
|
using namespace ast;
|
2013-10-09 22:57:10 +00:00
|
|
|
out_cmd->clear();
|
|
|
|
out_joined_args->clear();
|
2020-07-07 23:28:39 +00:00
|
|
|
*out_deco = statement_decoration_t::none;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
auto ast = ast_parse(src);
|
|
|
|
if (ast->errored()) return false;
|
2016-11-05 01:40:22 +00:00
|
|
|
|
|
|
|
// Get the statement. Should only have one.
|
2020-07-02 07:12:28 +00:00
|
|
|
const decorated_statement_t *statement = nullptr;
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
for (auto ast_traversal = new_ast_traversal(*ast->top());;) {
|
|
|
|
auto n = ast_traversal->next();
|
|
|
|
if (!n->has_value()) break;
|
|
|
|
if (const auto *tmp = n->try_as_decorated_statement()) {
|
2020-07-02 07:12:28 +00:00
|
|
|
if (statement) {
|
|
|
|
say(L"More than one decorated statement found in '%ls'", src.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
statement = tmp;
|
|
|
|
}
|
2016-11-05 01:40:22 +00:00
|
|
|
}
|
2020-12-25 08:25:08 +00:00
|
|
|
if (!statement) {
|
|
|
|
say(L"No decorated statement found in '%ls'", src.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-05 01:40:22 +00:00
|
|
|
|
2018-01-13 23:36:14 +00:00
|
|
|
// Return its decoration and command.
|
2020-07-02 07:12:28 +00:00
|
|
|
*out_deco = statement->decoration();
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
*out_cmd = *statement->command().source(src);
|
2016-11-05 01:40:22 +00:00
|
|
|
|
|
|
|
// Return arguments separated by spaces.
|
2018-01-16 06:13:37 +00:00
|
|
|
bool first = true;
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
for (size_t i = 0; i < statement->args_or_redirs().count(); i++) {
|
|
|
|
const ast::argument_or_redirection_t &arg = *statement->args_or_redirs().at(i);
|
2020-07-02 07:12:28 +00:00
|
|
|
if (!arg.is_argument()) continue;
|
2018-01-16 06:13:37 +00:00
|
|
|
if (!first) out_joined_args->push_back(L' ');
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
out_joined_args->append(*arg.ptr()->source(src));
|
2018-01-16 06:13:37 +00:00
|
|
|
first = false;
|
2016-11-05 01:40:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2013-10-09 22:57:10 +00:00
|
|
|
}
|
|
|
|
|
2018-01-16 06:13:37 +00:00
|
|
|
// Verify that 'function -h' and 'function --help' are plain statements but 'function --foo' is
|
|
|
|
// not (issue #1240).
|
2020-07-02 07:12:28 +00:00
|
|
|
template <ast::type_t Type>
|
2018-01-16 06:13:37 +00:00
|
|
|
static void check_function_help(const wchar_t *src) {
|
2020-07-02 07:12:28 +00:00
|
|
|
using namespace ast;
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
auto ast = ast_parse(src);
|
|
|
|
if (ast->errored()) {
|
2018-01-16 06:13:37 +00:00
|
|
|
err(L"Failed to parse '%ls'", src);
|
|
|
|
}
|
|
|
|
|
2020-07-02 07:12:28 +00:00
|
|
|
int count = 0;
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
for (auto ast_traversal = new_ast_traversal(*ast->top());;) {
|
|
|
|
auto node = ast_traversal->next();
|
|
|
|
if (!node->has_value()) break;
|
|
|
|
count += (node->typ() == Type);
|
2020-07-02 07:12:28 +00:00
|
|
|
}
|
|
|
|
if (count == 0) {
|
|
|
|
err(L"Failed to find node of type '%ls'", ast_type_to_string(Type));
|
|
|
|
} else if (count > 1) {
|
|
|
|
err(L"Found too many nodes of type '%ls'", ast_type_to_string(Type));
|
2018-01-16 06:13:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Test the LL2 (two token lookahead) nature of the parser by exercising the special builtin and
|
|
|
|
// command handling. In particular, 'command foo' should be a decorated statement 'foo' but 'command
|
|
|
|
// -help' should be an undecorated statement 'command' with argument '--help', and NOT attempt to
|
|
|
|
// run a command called '--help'.
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_new_parser_ll2() {
|
2013-10-09 22:57:10 +00:00
|
|
|
say(L"Testing parser two-token lookahead");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
const struct {
|
2013-10-09 22:57:10 +00:00
|
|
|
wcstring src;
|
|
|
|
wcstring cmd;
|
|
|
|
wcstring args;
|
2023-02-05 08:35:06 +00:00
|
|
|
statement_decoration_t deco;
|
2020-07-07 23:28:39 +00:00
|
|
|
} tests[] = {{L"echo hello", L"echo", L"hello", statement_decoration_t::none},
|
|
|
|
{L"command echo hello", L"echo", L"hello", statement_decoration_t::command},
|
|
|
|
{L"exec echo hello", L"echo", L"hello", statement_decoration_t::exec},
|
|
|
|
{L"command command hello", L"command", L"hello", statement_decoration_t::command},
|
|
|
|
{L"builtin command hello", L"command", L"hello", statement_decoration_t::builtin},
|
|
|
|
{L"command --help", L"command", L"--help", statement_decoration_t::none},
|
|
|
|
{L"command -h", L"command", L"-h", statement_decoration_t::none},
|
|
|
|
{L"command", L"command", L"", statement_decoration_t::none},
|
|
|
|
{L"command -", L"command", L"-", statement_decoration_t::none},
|
|
|
|
{L"command --", L"command", L"--", statement_decoration_t::none},
|
|
|
|
{L"builtin --names", L"builtin", L"--names", statement_decoration_t::none},
|
|
|
|
{L"function", L"function", L"", statement_decoration_t::none},
|
|
|
|
{L"function --help", L"function", L"--help", statement_decoration_t::none}};
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-06-30 22:29:06 +00:00
|
|
|
for (const auto &test : tests) {
|
2013-10-09 22:57:10 +00:00
|
|
|
wcstring cmd, args;
|
2023-02-05 08:35:06 +00:00
|
|
|
statement_decoration_t deco = statement_decoration_t::none;
|
2020-06-30 22:29:06 +00:00
|
|
|
bool success = test_1_parse_ll2(test.src, &cmd, &args, &deco);
|
|
|
|
if (!success) err(L"Parse of '%ls' failed on line %ld", test.cmd.c_str(), (long)__LINE__);
|
|
|
|
if (cmd != test.cmd)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"When parsing '%ls', expected command '%ls' but got '%ls' on line %ld",
|
2020-06-30 22:29:06 +00:00
|
|
|
test.src.c_str(), test.cmd.c_str(), cmd.c_str(), (long)__LINE__);
|
|
|
|
if (args != test.args)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"When parsing '%ls', expected args '%ls' but got '%ls' on line %ld",
|
2020-06-30 22:29:06 +00:00
|
|
|
test.src.c_str(), test.args.c_str(), args.c_str(), (long)__LINE__);
|
|
|
|
if (deco != test.deco)
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"When parsing '%ls', expected decoration %d but got %d on line %ld",
|
2020-06-30 22:29:06 +00:00
|
|
|
test.src.c_str(), (int)test.deco, (int)deco, (long)__LINE__);
|
2013-10-09 22:57:10 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-07-02 07:12:28 +00:00
|
|
|
check_function_help<ast::type_t::decorated_statement>(L"function -h");
|
|
|
|
check_function_help<ast::type_t::decorated_statement>(L"function --help");
|
|
|
|
check_function_help<ast::type_t::function_header>(L"function --foo; end");
|
|
|
|
check_function_help<ast::type_t::function_header>(L"function foo; end");
|
2013-10-09 22:57:10 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_new_parser_ad_hoc() {
|
2020-07-02 07:12:28 +00:00
|
|
|
using namespace ast;
|
2016-05-01 01:37:19 +00:00
|
|
|
// Very ad-hoc tests for issues encountered.
|
2013-12-08 21:41:12 +00:00
|
|
|
say(L"Testing new parser ad hoc tests");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Ensure that 'case' terminates a job list.
|
2013-12-08 21:41:12 +00:00
|
|
|
const wcstring src = L"switch foo ; case bar; case baz; end";
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
auto ast = ast_parse(src);
|
|
|
|
if (ast->errored()) {
|
2013-12-08 21:41:12 +00:00
|
|
|
err(L"Parsing failed");
|
2013-06-15 22:21:35 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-07-02 07:12:28 +00:00
|
|
|
// Expect two case_item_lists. The bug was that we'd
|
2016-05-01 01:37:19 +00:00
|
|
|
// try to run a command 'case'.
|
2020-07-02 07:12:28 +00:00
|
|
|
int count = 0;
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
for (auto ast_traversal = new_ast_traversal(*ast->top());;) {
|
|
|
|
auto n = ast_traversal->next();
|
|
|
|
if (!n->has_value()) break;
|
|
|
|
count += (n->typ() == type_t::case_item);
|
2020-07-02 07:12:28 +00:00
|
|
|
}
|
|
|
|
if (count != 2) {
|
|
|
|
err(L"Expected 2 case item nodes, found %d", count);
|
2013-06-15 22:21:35 +00:00
|
|
|
}
|
2020-07-05 18:36:13 +00:00
|
|
|
|
|
|
|
// Ensure that naked variable assignments don't hang.
|
|
|
|
// The bug was that "a=" would produce an error but not be consumed,
|
|
|
|
// leading to an infinite loop.
|
|
|
|
|
|
|
|
// By itself it should produce an error.
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
ast = ast_parse(L"a=");
|
|
|
|
do_test(ast->errored());
|
2020-07-05 18:36:13 +00:00
|
|
|
|
|
|
|
// If we are leaving things unterminated, this should not produce an error.
|
|
|
|
// i.e. when typing "a=" at the command line, it should be treated as valid
|
|
|
|
// because we don't want to color it as an error.
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
ast = ast_parse(L"a=", parse_flag_leave_unterminated);
|
|
|
|
do_test(!ast->errored());
|
2021-02-09 21:06:59 +00:00
|
|
|
|
2023-02-05 08:35:06 +00:00
|
|
|
auto errors = new_parse_error_list();
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
ast = ast_parse(L"begin; echo (", parse_flag_leave_unterminated, &*errors);
|
2023-02-05 08:35:06 +00:00
|
|
|
do_test(errors->size() == 1 &&
|
|
|
|
errors->at(0)->code() == parse_error_code_t::tokenizer_unterminated_subshell);
|
2021-02-09 21:06:59 +00:00
|
|
|
|
2023-02-05 08:35:06 +00:00
|
|
|
errors->clear();
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
ast = ast_parse(L"for x in (", parse_flag_leave_unterminated, &*errors);
|
2023-02-05 08:35:06 +00:00
|
|
|
do_test(errors->size() == 1 &&
|
|
|
|
errors->at(0)->code() == parse_error_code_t::tokenizer_unterminated_subshell);
|
2021-02-09 21:36:27 +00:00
|
|
|
|
2023-02-05 08:35:06 +00:00
|
|
|
errors->clear();
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
ast = ast_parse(L"begin; echo '", parse_flag_leave_unterminated, &*errors);
|
2023-02-05 08:35:06 +00:00
|
|
|
do_test(errors->size() == 1 &&
|
|
|
|
errors->at(0)->code() == parse_error_code_t::tokenizer_unterminated_quote);
|
2013-06-09 02:20:26 +00:00
|
|
|
}
|
2007-05-10 19:11:28 +00:00
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_new_parser_errors() {
|
2013-12-09 05:54:06 +00:00
|
|
|
say(L"Testing new parser error reporting");
|
2016-05-01 01:37:19 +00:00
|
|
|
const struct {
|
2013-12-09 05:54:06 +00:00
|
|
|
const wchar_t *src;
|
|
|
|
parse_error_code_t code;
|
2016-05-01 01:37:19 +00:00
|
|
|
} tests[] = {
|
2023-02-07 22:51:47 +00:00
|
|
|
{L"echo 'abc", parse_error_code_t::tokenizer_unterminated_quote},
|
|
|
|
{L"'", parse_error_code_t::tokenizer_unterminated_quote},
|
|
|
|
{L"echo (abc", parse_error_code_t::tokenizer_unterminated_subshell},
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2023-02-07 22:51:47 +00:00
|
|
|
{L"end", parse_error_code_t::unbalancing_end},
|
|
|
|
{L"echo hi ; end", parse_error_code_t::unbalancing_end},
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2023-02-07 22:51:47 +00:00
|
|
|
{L"else", parse_error_code_t::unbalancing_else},
|
|
|
|
{L"if true ; end ; else", parse_error_code_t::unbalancing_else},
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2023-02-07 22:51:47 +00:00
|
|
|
{L"case", parse_error_code_t::unbalancing_case},
|
|
|
|
{L"if true ; case ; end", parse_error_code_t::generic},
|
2020-07-03 18:16:51 +00:00
|
|
|
|
2023-02-07 22:51:47 +00:00
|
|
|
{L"true | and", parse_error_code_t::andor_in_pipeline},
|
2020-07-05 18:36:13 +00:00
|
|
|
|
2023-02-07 22:51:47 +00:00
|
|
|
{L"a=", parse_error_code_t::bare_variable_assignment},
|
2013-12-09 05:54:06 +00:00
|
|
|
};
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-06-30 22:29:06 +00:00
|
|
|
for (const auto &test : tests) {
|
|
|
|
const wcstring src = test.src;
|
|
|
|
parse_error_code_t expected_code = test.code;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2023-02-05 08:35:06 +00:00
|
|
|
auto errors = new_parse_error_list();
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
auto ast = ast_parse(src, parse_flag_none, &*errors);
|
|
|
|
if (!ast->errored()) {
|
2013-12-09 05:54:06 +00:00
|
|
|
err(L"Source '%ls' was expected to fail to parse, but succeeded", src.c_str());
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2023-02-05 08:35:06 +00:00
|
|
|
if (errors->size() != 1) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"Source '%ls' was expected to produce 1 error, but instead produced %lu errors",
|
2023-02-05 08:35:06 +00:00
|
|
|
src.c_str(), errors->size());
|
|
|
|
for (size_t i = 0; i < errors->size(); i++) {
|
|
|
|
fprintf(stderr, "%ls\n", errors->at(i)->describe(src, false)->c_str());
|
2020-07-02 07:12:28 +00:00
|
|
|
}
|
2023-02-05 08:35:06 +00:00
|
|
|
} else if (errors->at(0)->code() != expected_code) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"Source '%ls' was expected to produce error code %lu, but instead produced error "
|
|
|
|
L"code %lu",
|
2023-02-05 08:35:06 +00:00
|
|
|
src.c_str(), expected_code, (unsigned long)errors->at(0)->code());
|
|
|
|
for (size_t i = 0; i < errors->size(); i++) {
|
|
|
|
err(L"\t\t%ls", errors->at(i)->describe(src, true)->c_str());
|
2013-12-09 05:54:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-04-26 01:47:17 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Given a format string, returns a list of non-empty strings separated by format specifiers. The
|
|
|
|
// format specifiers themselves are omitted.
|
2023-04-18 22:19:10 +00:00
|
|
|
static std::vector<wcstring> separate_by_format_specifiers(const wchar_t *format) {
|
|
|
|
std::vector<wcstring> result;
|
2015-04-26 01:47:17 +00:00
|
|
|
const wchar_t *cursor = format;
|
2019-03-12 21:06:01 +00:00
|
|
|
const wchar_t *end = format + std::wcslen(format);
|
2016-05-01 01:37:19 +00:00
|
|
|
while (cursor < end) {
|
2019-03-12 21:06:01 +00:00
|
|
|
const wchar_t *next_specifier = std::wcschr(cursor, '%');
|
2021-08-17 22:57:52 +00:00
|
|
|
if (next_specifier == nullptr) {
|
2015-04-26 01:47:17 +00:00
|
|
|
next_specifier = end;
|
|
|
|
}
|
2021-08-17 22:57:52 +00:00
|
|
|
assert(next_specifier != nullptr);
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Don't return empty strings.
|
|
|
|
if (next_specifier > cursor) {
|
2021-08-17 09:20:28 +00:00
|
|
|
result.emplace_back(cursor, next_specifier - cursor);
|
2015-04-26 01:47:17 +00:00
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Walk over the format specifier (if any).
|
2015-04-26 01:47:17 +00:00
|
|
|
cursor = next_specifier;
|
2016-11-05 01:40:22 +00:00
|
|
|
if (*cursor != '%') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor++;
|
|
|
|
// Flag
|
2019-03-12 21:06:01 +00:00
|
|
|
if (std::wcschr(L"#0- +'", *cursor)) cursor++;
|
2016-11-05 01:40:22 +00:00
|
|
|
// Minimum field width
|
|
|
|
while (iswdigit(*cursor)) cursor++;
|
|
|
|
// Precision
|
|
|
|
if (*cursor == L'.') {
|
2015-04-26 01:47:17 +00:00
|
|
|
cursor++;
|
2016-05-01 01:37:19 +00:00
|
|
|
while (iswdigit(*cursor)) cursor++;
|
2015-04-26 01:47:17 +00:00
|
|
|
}
|
2016-11-05 01:40:22 +00:00
|
|
|
// Length modifier
|
2019-03-12 21:06:01 +00:00
|
|
|
if (!std::wcsncmp(cursor, L"ll", 2) || !std::wcsncmp(cursor, L"hh", 2)) {
|
2016-11-05 01:40:22 +00:00
|
|
|
cursor += 2;
|
2019-03-12 21:06:01 +00:00
|
|
|
} else if (std::wcschr(L"hljtzqL", *cursor)) {
|
2016-11-05 01:40:22 +00:00
|
|
|
cursor++;
|
|
|
|
}
|
|
|
|
// The format specifier itself. We allow any character except NUL.
|
|
|
|
if (*cursor != L'\0') {
|
|
|
|
cursor += 1;
|
|
|
|
}
|
|
|
|
assert(cursor <= end);
|
2015-04-26 01:47:17 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Given a format string 'format', return true if the string may have been produced by that format
|
|
|
|
// string. We do this by splitting the format string around the format specifiers, and then ensuring
|
|
|
|
// that each of the remaining chunks is found (in order) in the string.
|
|
|
|
static bool string_matches_format(const wcstring &string, const wchar_t *format) {
|
2015-04-26 01:47:17 +00:00
|
|
|
bool result = true;
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> components = separate_by_format_specifiers(format);
|
2015-04-26 01:47:17 +00:00
|
|
|
size_t idx = 0;
|
2020-06-30 22:29:06 +00:00
|
|
|
for (const auto &component : components) {
|
2015-04-26 01:47:17 +00:00
|
|
|
size_t where = string.find(component, idx);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (where == wcstring::npos) {
|
2015-04-26 01:47:17 +00:00
|
|
|
result = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
idx = where + component.size();
|
|
|
|
assert(idx <= string.size());
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static void test_error_messages() {
|
2015-04-26 01:47:17 +00:00
|
|
|
say(L"Testing error messages");
|
2016-05-01 01:37:19 +00:00
|
|
|
const struct error_test_t {
|
2015-04-26 01:47:17 +00:00
|
|
|
const wchar_t *src;
|
|
|
|
const wchar_t *error_text_format;
|
2016-05-01 01:37:19 +00:00
|
|
|
} error_tests[] = {{L"echo $^", ERROR_BAD_VAR_CHAR1},
|
|
|
|
{L"echo foo${a}bar", ERROR_BRACKETED_VARIABLE1},
|
|
|
|
{L"echo foo\"${a}\"bar", ERROR_BRACKETED_VARIABLE_QUOTED1},
|
|
|
|
{L"echo foo\"${\"bar", ERROR_BAD_VAR_CHAR1},
|
|
|
|
{L"echo $?", ERROR_NOT_STATUS},
|
|
|
|
{L"echo $$", ERROR_NOT_PID},
|
|
|
|
{L"echo $#", ERROR_NOT_ARGV_COUNT},
|
|
|
|
{L"echo $@", ERROR_NOT_ARGV_AT},
|
|
|
|
{L"echo $*", ERROR_NOT_ARGV_STAR},
|
|
|
|
{L"echo $", ERROR_NO_VAR_NAME},
|
|
|
|
{L"echo foo\"$\"bar", ERROR_NO_VAR_NAME},
|
|
|
|
{L"echo \"foo\"$\"bar\"", ERROR_NO_VAR_NAME},
|
2021-07-02 21:11:03 +00:00
|
|
|
{L"echo foo $ bar", ERROR_NO_VAR_NAME}};
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2023-02-05 08:35:06 +00:00
|
|
|
auto errors = new_parse_error_list();
|
2020-06-30 22:29:06 +00:00
|
|
|
for (const auto &test : error_tests) {
|
2023-02-05 08:35:06 +00:00
|
|
|
errors->clear();
|
|
|
|
parse_util_detect_errors(test.src, &*errors);
|
|
|
|
do_test(!errors->empty());
|
|
|
|
if (!errors->empty()) {
|
|
|
|
do_test1(string_matches_format(*errors->at(0)->text(), test.error_text_format),
|
|
|
|
test.src);
|
2015-04-26 01:47:17 +00:00
|
|
|
}
|
|
|
|
}
|
2013-12-09 05:54:06 +00:00
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_highlighting() {
|
2013-08-11 07:35:00 +00:00
|
|
|
say(L"Testing syntax highlighting");
|
2021-03-13 16:49:24 +00:00
|
|
|
if (!pushd("test/fish_highlight_test/")) return;
|
|
|
|
cleanup_t pop{[] { popd(); }};
|
|
|
|
if (system("mkdir -p dir")) err(L"mkdir failed");
|
2022-10-26 13:46:56 +00:00
|
|
|
if (system("mkdir -p cdpath-entry/dir-in-cdpath")) err(L"mkdir failed");
|
2021-03-13 16:49:24 +00:00
|
|
|
if (system("touch foo")) err(L"touch failed");
|
|
|
|
if (system("touch bar")) err(L"touch failed");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Here are the components of our source and the colors we expect those to be.
|
|
|
|
struct highlight_component_t {
|
2013-08-11 07:35:00 +00:00
|
|
|
const wchar_t *txt;
|
2019-03-04 01:34:00 +00:00
|
|
|
highlight_spec_t color;
|
2018-09-01 18:45:15 +00:00
|
|
|
bool nospace;
|
2019-03-04 01:34:00 +00:00
|
|
|
highlight_component_t(const wchar_t *txt, highlight_spec_t color, bool nospace = false)
|
2018-09-01 18:45:15 +00:00
|
|
|
: txt(txt), color(color), nospace(nospace) {}
|
2013-08-11 07:35:00 +00:00
|
|
|
};
|
2018-09-01 18:45:15 +00:00
|
|
|
const bool ns = true;
|
|
|
|
|
2018-09-01 17:17:44 +00:00
|
|
|
using highlight_component_list_t = std::vector<highlight_component_t>;
|
|
|
|
std::vector<highlight_component_list_t> highlight_tests;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-03-04 01:34:00 +00:00
|
|
|
highlight_spec_t param_valid_path{highlight_role_t::param};
|
|
|
|
param_valid_path.valid_path = true;
|
|
|
|
|
|
|
|
highlight_tests.push_back({{L"echo", highlight_role_t::command},
|
2021-03-13 16:49:24 +00:00
|
|
|
{L"./foo", param_valid_path},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"&", highlight_role_t::statement_terminator}});
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2018-09-01 17:17:44 +00:00
|
|
|
highlight_tests.push_back({
|
2021-02-04 19:22:32 +00:00
|
|
|
{L"command", highlight_role_t::keyword},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"abc", highlight_role_t::param},
|
2021-03-13 16:49:24 +00:00
|
|
|
{L"foo", param_valid_path},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"&", highlight_role_t::statement_terminator},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2021-06-29 17:30:27 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"foo&bar", highlight_role_t::param},
|
|
|
|
{L"foo", highlight_role_t::param, /*nospace=*/true},
|
|
|
|
{L"&", highlight_role_t::statement_terminator},
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"&>", highlight_role_t::redirection},
|
|
|
|
});
|
|
|
|
|
2018-09-01 17:17:44 +00:00
|
|
|
highlight_tests.push_back({
|
2021-02-04 19:22:32 +00:00
|
|
|
{L"if command", highlight_role_t::keyword},
|
|
|
|
{L"ls", highlight_role_t::command},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"; ", highlight_role_t::statement_terminator},
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"abc", highlight_role_t::param},
|
|
|
|
{L"; ", highlight_role_t::statement_terminator},
|
|
|
|
{L"/bin/definitely_not_a_command", highlight_role_t::error},
|
|
|
|
{L"; ", highlight_role_t::statement_terminator},
|
2021-02-04 19:22:32 +00:00
|
|
|
{L"end", highlight_role_t::keyword},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Verify that cd shows errors for non-directories.
|
2018-09-01 17:17:44 +00:00
|
|
|
highlight_tests.push_back({
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"cd", highlight_role_t::command},
|
2021-03-13 16:49:24 +00:00
|
|
|
{L"dir", param_valid_path},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2018-09-01 17:17:44 +00:00
|
|
|
highlight_tests.push_back({
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"cd", highlight_role_t::command},
|
2021-03-13 16:49:24 +00:00
|
|
|
{L"foo", highlight_role_t::error},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2018-09-01 17:17:44 +00:00
|
|
|
highlight_tests.push_back({
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"cd", highlight_role_t::command},
|
2021-10-19 15:20:21 +00:00
|
|
|
{L"--help", highlight_role_t::option},
|
|
|
|
{L"-h", highlight_role_t::option},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"definitely_not_a_directory", highlight_role_t::error},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
2016-05-01 01:37:19 +00:00
|
|
|
|
2022-10-26 13:46:56 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"cd", highlight_role_t::command},
|
|
|
|
{L"dir-in-cdpath", param_valid_path},
|
|
|
|
});
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Command substitutions.
|
2018-09-01 17:17:44 +00:00
|
|
|
highlight_tests.push_back({
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"param1", highlight_role_t::param},
|
2021-10-19 15:30:45 +00:00
|
|
|
{L"-l", highlight_role_t::option},
|
|
|
|
{L"--", highlight_role_t::option},
|
|
|
|
{L"-l", highlight_role_t::param},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"(", highlight_role_t::operat},
|
|
|
|
{L"ls", highlight_role_t::command},
|
2021-10-19 15:30:45 +00:00
|
|
|
{L"-l", highlight_role_t::option},
|
|
|
|
{L"--", highlight_role_t::option},
|
|
|
|
{L"-l", highlight_role_t::param},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"param2", highlight_role_t::param},
|
|
|
|
{L")", highlight_role_t::operat},
|
|
|
|
{L"|", highlight_role_t::statement_terminator},
|
|
|
|
{L"cat", highlight_role_t::command},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
2021-07-02 21:11:03 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"true", highlight_role_t::command},
|
|
|
|
{L"$(", highlight_role_t::operat},
|
|
|
|
{L"true", highlight_role_t::command},
|
|
|
|
{L")", highlight_role_t::operat},
|
|
|
|
});
|
2021-07-02 21:11:03 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"true", highlight_role_t::command},
|
|
|
|
{L"\"before", highlight_role_t::quote},
|
|
|
|
{L"$(", highlight_role_t::operat},
|
|
|
|
{L"true", highlight_role_t::command},
|
|
|
|
{L"param1", highlight_role_t::param},
|
|
|
|
{L")", highlight_role_t::operat},
|
|
|
|
{L"after\"", highlight_role_t::quote},
|
|
|
|
{L"param2", highlight_role_t::param},
|
|
|
|
});
|
2021-10-31 12:44:38 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"true", highlight_role_t::command},
|
|
|
|
{L"\"", highlight_role_t::error},
|
|
|
|
{L"unclosed quote", highlight_role_t::quote},
|
|
|
|
{L"$(", highlight_role_t::operat},
|
|
|
|
{L"true", highlight_role_t::command},
|
|
|
|
{L")", highlight_role_t::operat},
|
|
|
|
});
|
2016-05-01 01:37:19 +00:00
|
|
|
|
|
|
|
// Redirections substitutions.
|
2018-09-01 17:17:44 +00:00
|
|
|
highlight_tests.push_back({
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"param1", highlight_role_t::param},
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Input redirection.
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"<", highlight_role_t::redirection},
|
|
|
|
{L"/bin/echo", highlight_role_t::redirection},
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Output redirection to a valid fd.
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"1>&2", highlight_role_t::redirection},
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Output redirection to an invalid fd.
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"2>&", highlight_role_t::redirection},
|
|
|
|
{L"LOL", highlight_role_t::error},
|
2013-10-13 23:58:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Just a param, not a redirection.
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"test/blah", highlight_role_t::param},
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Input redirection from directory.
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"<", highlight_role_t::redirection},
|
|
|
|
{L"test/", highlight_role_t::error},
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Output redirection to an invalid path.
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"3>", highlight_role_t::redirection},
|
|
|
|
{L"/not/a/valid/path/nope", highlight_role_t::error},
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Output redirection to directory.
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"3>", highlight_role_t::redirection},
|
|
|
|
{L"test/nope/", highlight_role_t::error},
|
2013-10-13 23:58:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Redirections to overflow fd.
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"99999999999999999999>&2", highlight_role_t::error},
|
|
|
|
{L"2>&", highlight_role_t::redirection},
|
|
|
|
{L"99999999999999999999", highlight_role_t::error},
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Output redirection containing a command substitution.
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"4>", highlight_role_t::redirection},
|
|
|
|
{L"(", highlight_role_t::operat},
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"test/somewhere", highlight_role_t::param},
|
|
|
|
{L")", highlight_role_t::operat},
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Just another param.
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"param2", highlight_role_t::param},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
|
|
|
|
2021-02-01 06:08:05 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"for", highlight_role_t::keyword},
|
|
|
|
{L"x", highlight_role_t::param},
|
|
|
|
{L"in", highlight_role_t::keyword},
|
|
|
|
{L"set-by-for-1", highlight_role_t::param},
|
|
|
|
{L"set-by-for-2", highlight_role_t::param},
|
|
|
|
{L";", highlight_role_t::statement_terminator},
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L">", highlight_role_t::redirection},
|
|
|
|
{L"$x", highlight_role_t::redirection},
|
|
|
|
{L";", highlight_role_t::statement_terminator},
|
|
|
|
{L"end", highlight_role_t::keyword},
|
|
|
|
});
|
|
|
|
|
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"set", highlight_role_t::command},
|
|
|
|
{L"x", highlight_role_t::param},
|
|
|
|
{L"set-by-set", highlight_role_t::param},
|
|
|
|
{L";", highlight_role_t::statement_terminator},
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L">", highlight_role_t::redirection},
|
|
|
|
{L"$x", highlight_role_t::redirection},
|
|
|
|
{L"2>", highlight_role_t::redirection},
|
|
|
|
{L"$totally_not_x", highlight_role_t::error},
|
|
|
|
{L"<", highlight_role_t::redirection},
|
|
|
|
{L"$x_but_its_an_impostor", highlight_role_t::error},
|
|
|
|
});
|
|
|
|
|
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"x", highlight_role_t::param, ns},
|
|
|
|
{L"=", highlight_role_t::operat, ns},
|
|
|
|
{L"set-by-variable-override", highlight_role_t::param, ns},
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L">", highlight_role_t::redirection},
|
|
|
|
{L"$x", highlight_role_t::redirection},
|
|
|
|
});
|
|
|
|
|
2018-09-01 17:17:44 +00:00
|
|
|
highlight_tests.push_back({
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"end", highlight_role_t::error},
|
|
|
|
{L";", highlight_role_t::statement_terminator},
|
2021-02-04 19:22:32 +00:00
|
|
|
{L"if", highlight_role_t::keyword},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"end", highlight_role_t::error},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
highlight_tests.push_back({
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"echo", highlight_role_t::command},
|
2019-07-24 19:28:05 +00:00
|
|
|
{L"'", highlight_role_t::error},
|
|
|
|
{L"single_quote", highlight_role_t::quote},
|
|
|
|
{L"$stuff", highlight_role_t::quote},
|
|
|
|
});
|
|
|
|
|
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"\"", highlight_role_t::error},
|
|
|
|
{L"double_quote", highlight_role_t::quote},
|
|
|
|
{L"$stuff", highlight_role_t::operat},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
highlight_tests.push_back({
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"$foo", highlight_role_t::operat},
|
|
|
|
{L"\"", highlight_role_t::quote},
|
|
|
|
{L"$bar", highlight_role_t::operat},
|
|
|
|
{L"\"", highlight_role_t::quote},
|
|
|
|
{L"$baz[", highlight_role_t::operat},
|
|
|
|
{L"1 2..3", highlight_role_t::param},
|
|
|
|
{L"]", highlight_role_t::operat},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
highlight_tests.push_back({
|
2021-02-04 19:22:32 +00:00
|
|
|
{L"for", highlight_role_t::keyword},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"i", highlight_role_t::param},
|
2021-02-04 19:22:32 +00:00
|
|
|
{L"in", highlight_role_t::keyword},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"1 2 3", highlight_role_t::param},
|
|
|
|
{L";", highlight_role_t::statement_terminator},
|
2021-02-04 19:22:32 +00:00
|
|
|
{L"end", highlight_role_t::keyword},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
highlight_tests.push_back({
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"$$foo[", highlight_role_t::operat},
|
|
|
|
{L"1", highlight_role_t::param},
|
|
|
|
{L"][", highlight_role_t::operat},
|
|
|
|
{L"2", highlight_role_t::param},
|
|
|
|
{L"]", highlight_role_t::operat},
|
|
|
|
{L"[3]", highlight_role_t::param}, // two dollar signs, so last one is not an expansion
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
highlight_tests.push_back({
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"cat", highlight_role_t::command},
|
2019-05-19 08:03:56 +00:00
|
|
|
{L"/dev/null", param_valid_path},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"|", highlight_role_t::statement_terminator},
|
2018-12-28 16:57:53 +00:00
|
|
|
// This is bogus, but we used to use "less" here and that doesn't have to be installed.
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"cat", highlight_role_t::command},
|
|
|
|
{L"2>", highlight_role_t::redirection},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2022-10-26 10:29:36 +00:00
|
|
|
// Highlight path-prefixes only at the cursor.
|
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"cat", highlight_role_t::command},
|
|
|
|
{L"/dev/nu", highlight_role_t::param},
|
|
|
|
{L"/dev/nu", param_valid_path},
|
|
|
|
});
|
|
|
|
|
2018-09-01 17:17:44 +00:00
|
|
|
highlight_tests.push_back({
|
2021-02-04 19:22:32 +00:00
|
|
|
{L"if", highlight_role_t::keyword},
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"true", highlight_role_t::command},
|
|
|
|
{L"&&", highlight_role_t::operat},
|
|
|
|
{L"false", highlight_role_t::command},
|
|
|
|
{L";", highlight_role_t::statement_terminator},
|
|
|
|
{L"or", highlight_role_t::operat},
|
|
|
|
{L"false", highlight_role_t::command},
|
|
|
|
{L"||", highlight_role_t::operat},
|
|
|
|
{L"true", highlight_role_t::command},
|
|
|
|
{L";", highlight_role_t::statement_terminator},
|
|
|
|
{L"and", highlight_role_t::operat},
|
|
|
|
{L"not", highlight_role_t::operat},
|
|
|
|
{L"!", highlight_role_t::operat},
|
|
|
|
{L"true", highlight_role_t::command},
|
|
|
|
{L";", highlight_role_t::statement_terminator},
|
2021-02-04 19:22:32 +00:00
|
|
|
{L"end", highlight_role_t::keyword},
|
2018-09-01 17:17:44 +00:00
|
|
|
});
|
|
|
|
|
2018-10-10 23:25:21 +00:00
|
|
|
highlight_tests.push_back({
|
2019-03-04 01:34:00 +00:00
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"%self", highlight_role_t::operat},
|
|
|
|
{L"not%self", highlight_role_t::param},
|
|
|
|
{L"self%not", highlight_role_t::param},
|
2018-10-10 23:25:21 +00:00
|
|
|
});
|
|
|
|
|
2019-10-14 22:45:40 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"false", highlight_role_t::command},
|
|
|
|
{L"&|", highlight_role_t::statement_terminator},
|
|
|
|
{L"true", highlight_role_t::command},
|
|
|
|
});
|
|
|
|
|
2019-10-27 22:44:08 +00:00
|
|
|
highlight_tests.push_back({
|
2020-08-10 23:41:56 +00:00
|
|
|
{L"HOME", highlight_role_t::param},
|
|
|
|
{L"=", highlight_role_t::operat, ns},
|
|
|
|
{L".", highlight_role_t::param, ns},
|
2020-08-11 19:27:32 +00:00
|
|
|
{L"VAR1", highlight_role_t::param},
|
|
|
|
{L"=", highlight_role_t::operat, ns},
|
|
|
|
{L"VAL1", highlight_role_t::param, ns},
|
|
|
|
{L"VAR", highlight_role_t::param},
|
|
|
|
{L"=", highlight_role_t::operat, ns},
|
2019-10-27 22:44:08 +00:00
|
|
|
{L"false", highlight_role_t::command},
|
|
|
|
{L"|&", highlight_role_t::error},
|
2019-10-27 23:08:49 +00:00
|
|
|
{L"true", highlight_role_t::command},
|
|
|
|
{L"stuff", highlight_role_t::param},
|
2019-10-27 22:44:08 +00:00
|
|
|
});
|
|
|
|
|
2019-11-09 00:53:13 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L")", highlight_role_t::error},
|
|
|
|
});
|
|
|
|
|
2020-06-20 22:27:15 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"stuff", highlight_role_t::param},
|
|
|
|
{L"# comment", highlight_role_t::comment},
|
|
|
|
});
|
|
|
|
|
2021-10-19 15:20:21 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"--", highlight_role_t::option},
|
|
|
|
{L"-s", highlight_role_t::param},
|
|
|
|
});
|
|
|
|
|
2021-03-21 23:00:29 +00:00
|
|
|
// Overlong paths don't crash (#7837).
|
|
|
|
const wcstring overlong = get_overlong_path();
|
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"touch", highlight_role_t::command},
|
|
|
|
{overlong.c_str(), highlight_role_t::param},
|
|
|
|
});
|
|
|
|
|
2020-07-05 18:36:13 +00:00
|
|
|
highlight_tests.push_back({
|
2020-08-10 23:41:56 +00:00
|
|
|
{L"a", highlight_role_t::param},
|
|
|
|
{L"=", highlight_role_t::operat, ns},
|
2020-07-05 18:36:13 +00:00
|
|
|
});
|
|
|
|
|
2021-11-16 06:52:12 +00:00
|
|
|
// Highlighting works across escaped line breaks (#8444).
|
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"$FISH_\\\n", highlight_role_t::operat},
|
|
|
|
{L"VERSION", highlight_role_t::operat, ns},
|
|
|
|
});
|
|
|
|
|
2018-09-11 03:57:10 +00:00
|
|
|
auto &vars = parser_t::principal_parser().vars();
|
2018-09-01 18:45:15 +00:00
|
|
|
// Verify variables and wildcards in commands using /bin/cat.
|
2018-09-11 03:57:10 +00:00
|
|
|
vars.set(L"VARIABLE_IN_COMMAND", ENV_LOCAL, {L"a"});
|
|
|
|
vars.set(L"VARIABLE_IN_COMMAND2", ENV_LOCAL, {L"at"});
|
2022-10-26 13:46:56 +00:00
|
|
|
vars.set(L"CDPATH", ENV_LOCAL, {L"./cdpath-entry"});
|
2018-09-01 18:45:15 +00:00
|
|
|
highlight_tests.push_back(
|
2019-03-04 01:34:00 +00:00
|
|
|
{{L"/bin/ca", highlight_role_t::command, ns}, {L"*", highlight_role_t::operat, ns}});
|
2018-09-01 18:45:15 +00:00
|
|
|
|
2019-03-04 01:34:00 +00:00
|
|
|
highlight_tests.push_back({{L"/bin/c", highlight_role_t::command, ns},
|
|
|
|
{L"{$VARIABLE_IN_COMMAND}", highlight_role_t::operat, ns},
|
|
|
|
{L"*", highlight_role_t::operat, ns}});
|
2018-09-01 18:45:15 +00:00
|
|
|
|
2019-03-04 01:34:00 +00:00
|
|
|
highlight_tests.push_back({{L"/bin/c", highlight_role_t::command, ns},
|
|
|
|
{L"{$VARIABLE_IN_COMMAND}", highlight_role_t::operat, ns},
|
|
|
|
{L"*", highlight_role_t::operat, ns}});
|
2018-09-01 18:45:15 +00:00
|
|
|
|
2019-03-04 01:34:00 +00:00
|
|
|
highlight_tests.push_back({{L"/bin/c", highlight_role_t::command, ns},
|
|
|
|
{L"$VARIABLE_IN_COMMAND2", highlight_role_t::operat, ns}});
|
2018-09-01 18:45:15 +00:00
|
|
|
|
2019-03-04 01:34:00 +00:00
|
|
|
highlight_tests.push_back({{L"$EMPTY_VARIABLE", highlight_role_t::error}});
|
|
|
|
highlight_tests.push_back({{L"\"$EMPTY_VARIABLE\"", highlight_role_t::error}});
|
2018-09-01 19:05:52 +00:00
|
|
|
|
2022-09-29 15:16:42 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"\\UFDFD", highlight_role_t::escape},
|
2022-10-26 12:00:44 +00:00
|
|
|
});
|
2022-09-29 15:16:42 +00:00
|
|
|
#if WCHAR_T_BITS > 16
|
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"\\U10FFFF", highlight_role_t::escape},
|
2022-10-26 12:00:44 +00:00
|
|
|
});
|
2022-09-29 15:16:42 +00:00
|
|
|
highlight_tests.push_back({
|
|
|
|
{L"echo", highlight_role_t::command},
|
|
|
|
{L"\\U110000", highlight_role_t::error},
|
2022-10-26 12:00:44 +00:00
|
|
|
});
|
2022-09-29 15:16:42 +00:00
|
|
|
#endif
|
2023-02-05 13:06:11 +00:00
|
|
|
|
2023-02-03 15:34:29 +00:00
|
|
|
bool saved_flag = feature_test(feature_flag_t::ampersand_nobg_in_token);
|
|
|
|
mutable_fish_features()->set(feature_flag_t::ampersand_nobg_in_token, true);
|
2018-09-01 17:17:44 +00:00
|
|
|
for (const highlight_component_list_t &components : highlight_tests) {
|
2016-05-01 01:37:19 +00:00
|
|
|
// Generate the text.
|
2013-08-11 07:35:00 +00:00
|
|
|
wcstring text;
|
2014-04-28 01:27:34 +00:00
|
|
|
std::vector<highlight_spec_t> expected_colors;
|
2018-09-01 18:45:15 +00:00
|
|
|
for (const highlight_component_t &comp : components) {
|
|
|
|
if (!text.empty() && !comp.nospace) {
|
2013-08-11 07:35:00 +00:00
|
|
|
text.push_back(L' ');
|
2021-08-17 20:36:52 +00:00
|
|
|
expected_colors.emplace_back();
|
2013-08-11 07:35:00 +00:00
|
|
|
}
|
2018-09-01 18:45:15 +00:00
|
|
|
text.append(comp.txt);
|
|
|
|
expected_colors.resize(text.size(), comp.color);
|
2013-08-11 07:35:00 +00:00
|
|
|
}
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(expected_colors.size() == text.size());
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2014-01-15 09:01:25 +00:00
|
|
|
std::vector<highlight_spec_t> colors(text.size());
|
2022-10-26 07:30:46 +00:00
|
|
|
highlight_shell(text, colors, operation_context_t{vars}, true /* io_ok */, text.size());
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
if (expected_colors.size() != colors.size()) {
|
|
|
|
err(L"Color vector has wrong size! Expected %lu, actual %lu", expected_colors.size(),
|
|
|
|
colors.size());
|
2013-08-11 07:35:00 +00:00
|
|
|
}
|
2014-01-24 02:19:52 +00:00
|
|
|
do_test(expected_colors.size() == colors.size());
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < text.size(); i++) {
|
2013-10-13 23:58:40 +00:00
|
|
|
// Hackish space handling. We don't care about the colors in spaces.
|
2016-05-01 01:37:19 +00:00
|
|
|
if (text.at(i) == L' ') continue;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2021-03-13 16:49:24 +00:00
|
|
|
if (expected_colors.at(i) != colors.at(i)) {
|
2021-10-19 15:27:35 +00:00
|
|
|
// Make a fancy caret under the token
|
|
|
|
auto e_col = expected_colors.at(i);
|
|
|
|
auto a_col = colors.at(i);
|
|
|
|
auto j = i + 1;
|
2021-11-08 19:36:01 +00:00
|
|
|
while (j < colors.size() && expected_colors.at(j) == e_col && colors.at(j) == a_col)
|
|
|
|
j++;
|
2021-10-19 15:27:35 +00:00
|
|
|
if (j == colors.size() - 1) j++;
|
2013-08-11 07:35:00 +00:00
|
|
|
const wcstring spaces(i, L' ');
|
2021-10-19 15:27:35 +00:00
|
|
|
const wcstring carets(j - i, L'^');
|
|
|
|
err(L"Wrong color in test at index %lu-%lu in text (expected %#x, actual "
|
|
|
|
L"%#x):\n%ls\n%ls%ls",
|
2021-11-08 19:36:01 +00:00
|
|
|
i, j - 1, expected_colors.at(i), colors.at(i), text.c_str(), spaces.c_str(),
|
|
|
|
carets.c_str());
|
2021-10-19 15:27:35 +00:00
|
|
|
i = j;
|
2013-08-11 07:35:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-03 15:34:29 +00:00
|
|
|
mutable_fish_features()->set(feature_flag_t::ampersand_nobg_in_token, saved_flag);
|
2018-09-11 03:57:10 +00:00
|
|
|
vars.remove(L"VARIABLE_IN_COMMAND", ENV_DEFAULT);
|
|
|
|
vars.remove(L"VARIABLE_IN_COMMAND2", ENV_DEFAULT);
|
2013-08-11 07:35:00 +00:00
|
|
|
}
|
|
|
|
|
2020-07-30 02:00:04 +00:00
|
|
|
static void test_wwrite_to_fd() {
|
|
|
|
say(L"Testing wwrite_to_fd");
|
|
|
|
char t[] = "/tmp/fish_test_wwrite.XXXXXX";
|
2020-08-07 02:05:23 +00:00
|
|
|
autoclose_fd_t tmpfd{mkstemp(t)};
|
|
|
|
if (!tmpfd.valid()) {
|
2020-07-30 02:00:04 +00:00
|
|
|
err(L"Unable to create temporary file");
|
|
|
|
return;
|
|
|
|
}
|
2020-08-07 02:05:23 +00:00
|
|
|
tmpfd.close();
|
|
|
|
|
2020-07-30 02:00:04 +00:00
|
|
|
size_t sizes[] = {0, 1, 2, 3, 5, 13, 23, 64, 128, 255, 4096, 4096 * 2};
|
|
|
|
for (size_t size : sizes) {
|
|
|
|
autoclose_fd_t fd{open(t, O_RDWR | O_TRUNC | O_CREAT, 0666)};
|
|
|
|
if (!fd.valid()) {
|
|
|
|
wperror(L"open");
|
|
|
|
err(L"Unable to open temporary file");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
wcstring input{};
|
|
|
|
for (size_t i = 0; i < size; i++) {
|
|
|
|
input.push_back(wchar_t(random()));
|
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t amt = wwrite_to_fd(input, fd.fd());
|
|
|
|
if (amt < 0) {
|
|
|
|
wperror(L"write");
|
|
|
|
err(L"Unable to write to temporary file");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::string narrow = wcs2string(input);
|
|
|
|
size_t expected_size = narrow.size();
|
2020-07-30 03:12:19 +00:00
|
|
|
do_test(static_cast<size_t>(amt) == expected_size);
|
2020-07-30 02:00:04 +00:00
|
|
|
|
|
|
|
if (lseek(fd.fd(), 0, SEEK_SET) < 0) {
|
|
|
|
wperror(L"seek");
|
|
|
|
err(L"Unable to seek temporary file");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string contents(expected_size, '\0');
|
|
|
|
ssize_t read_amt = read(fd.fd(), &contents[0], expected_size);
|
|
|
|
do_test(read_amt >= 0 && static_cast<size_t>(read_amt) == expected_size);
|
|
|
|
}
|
|
|
|
(void)remove(t);
|
|
|
|
}
|
|
|
|
|
2021-02-14 02:41:09 +00:00
|
|
|
maybe_t<int> builtin_string(parser_t &parser, io_streams_t &streams, const wchar_t **argv);
|
|
|
|
static void run_one_string_test(const wchar_t *const *argv_raw, int expected_rc,
|
2016-05-01 01:37:19 +00:00
|
|
|
const wchar_t *expected_out) {
|
2021-02-14 02:41:09 +00:00
|
|
|
// Copy to a null terminated array, as builtin_string may wish to rearrange our pointers.
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> argv_list(argv_raw, argv_raw + null_terminated_array_length(argv_raw));
|
2021-02-14 02:41:09 +00:00
|
|
|
null_terminated_array_t<wchar_t> argv(argv_list);
|
|
|
|
|
2018-09-09 08:36:21 +00:00
|
|
|
parser_t &parser = parser_t::principal_parser();
|
2021-02-04 23:18:34 +00:00
|
|
|
string_output_stream_t outs{};
|
2020-07-29 23:03:29 +00:00
|
|
|
null_output_stream_t errs{};
|
|
|
|
io_streams_t streams(outs, errs);
|
2016-05-01 01:37:19 +00:00
|
|
|
streams.stdin_is_directly_redirected = false; // read from argv instead of stdin
|
2021-02-14 02:41:09 +00:00
|
|
|
maybe_t<int> rc = builtin_string(parser, streams, argv.get());
|
|
|
|
|
2015-09-12 19:59:40 +00:00
|
|
|
wcstring args;
|
2021-02-14 02:41:09 +00:00
|
|
|
for (const wcstring &arg : argv_list) {
|
2022-07-25 14:25:04 +00:00
|
|
|
args += escape_string(arg) + L' ';
|
2015-09-12 19:59:40 +00:00
|
|
|
}
|
|
|
|
args.resize(args.size() - 1);
|
2021-02-14 02:41:09 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
if (rc != expected_rc) {
|
2022-10-08 16:56:38 +00:00
|
|
|
// The comparison above would have panicked if rc didn't have a value, so it's safe to
|
|
|
|
// assume it has one here:
|
|
|
|
std::wstring got = std::to_wstring(rc.value());
|
2020-07-18 17:25:43 +00:00
|
|
|
err(L"Test failed on line %lu: [%ls]: expected return code %d but got %s", __LINE__,
|
|
|
|
args.c_str(), expected_rc, got.c_str());
|
2020-07-29 23:03:29 +00:00
|
|
|
} else if (outs.contents() != expected_out) {
|
2016-05-01 01:37:19 +00:00
|
|
|
err(L"Test failed on line %lu: [%ls]: expected [%ls] but got [%ls]", __LINE__, args.c_str(),
|
2022-07-25 14:25:04 +00:00
|
|
|
escape_string(expected_out).c_str(), escape_string(outs.contents()).c_str());
|
2015-09-12 19:59:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_string() {
|
2018-09-28 02:22:55 +00:00
|
|
|
say(L"Testing builtin_string");
|
2018-05-06 02:11:57 +00:00
|
|
|
const struct string_test {
|
2015-09-12 19:59:40 +00:00
|
|
|
const wchar_t *argv[15];
|
|
|
|
int expected_rc;
|
|
|
|
const wchar_t *expected_out;
|
2021-12-30 19:24:56 +00:00
|
|
|
} string_tests[] = { //
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"escape", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"escape", L"", nullptr}, STATUS_CMD_OK, L"''\n"},
|
|
|
|
{{L"string", L"escape", L"-n", L"", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"escape", L"a", nullptr}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"escape", L"\x07", nullptr}, STATUS_CMD_OK, L"\\cg\n"},
|
|
|
|
{{L"string", L"escape", L"\"x\"", nullptr}, STATUS_CMD_OK, L"'\"x\"'\n"},
|
|
|
|
{{L"string", L"escape", L"hello world", nullptr}, STATUS_CMD_OK, L"'hello world'\n"},
|
|
|
|
{{L"string", L"escape", L"-n", L"hello world", nullptr}, STATUS_CMD_OK, L"hello\\ world\n"},
|
|
|
|
{{L"string", L"escape", L"hello", L"world", nullptr}, STATUS_CMD_OK, L"hello\nworld\n"},
|
|
|
|
{{L"string", L"escape", L"-n", L"~", nullptr}, STATUS_CMD_OK, L"\\~\n"},
|
|
|
|
|
|
|
|
{{L"string", L"join", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"join", L"", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"join", L"", L"", L"", L"", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"join", L"", L"a", L"b", L"c", nullptr}, STATUS_CMD_OK, L"abc\n"},
|
|
|
|
{{L"string", L"join", L".", L"fishshell", L"com", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"fishshell.com\n"},
|
|
|
|
{{L"string", L"join", L"/", L"usr", nullptr}, STATUS_CMD_ERROR, L"usr\n"},
|
|
|
|
{{L"string", L"join", L"/", L"usr", L"local", L"bin", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"usr/local/bin\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"join", L"...", L"3", L"2", L"1", nullptr}, STATUS_CMD_OK, L"3...2...1\n"},
|
|
|
|
{{L"string", L"join", L"-q", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"join", L"-q", L".", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"join", L"-q", L".", L".", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
|
|
|
|
{{L"string", L"length", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"length", L"", nullptr}, STATUS_CMD_ERROR, L"0\n"},
|
|
|
|
{{L"string", L"length", L"", L"", L"", nullptr}, STATUS_CMD_ERROR, L"0\n0\n0\n"},
|
|
|
|
{{L"string", L"length", L"a", nullptr}, STATUS_CMD_OK, L"1\n"},
|
2021-12-30 19:24:56 +00:00
|
|
|
#if WCHAR_T_BITS > 16
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"length", L"\U0002008A", nullptr}, STATUS_CMD_OK, L"1\n"},
|
2021-12-30 19:24:56 +00:00
|
|
|
#endif
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"length", L"um", L"dois", L"três", nullptr}, STATUS_CMD_OK, L"2\n4\n4\n"},
|
|
|
|
{{L"string", L"length", L"um", L"dois", L"três", nullptr}, STATUS_CMD_OK, L"2\n4\n4\n"},
|
|
|
|
{{L"string", L"length", L"-q", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"length", L"-q", L"", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"length", L"-q", L"a", nullptr}, STATUS_CMD_OK, L""},
|
|
|
|
|
|
|
|
{{L"string", L"match", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"match", L"", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"", L"", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"match", L"?", L"a", nullptr}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"match", L"*", L"", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"match", L"**", L"", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"match", L"*", L"xyzzy", nullptr}, STATUS_CMD_OK, L"xyzzy\n"},
|
|
|
|
{{L"string", L"match", L"**", L"plugh", nullptr}, STATUS_CMD_OK, L"plugh\n"},
|
|
|
|
{{L"string", L"match", L"a*b", L"axxb", nullptr}, STATUS_CMD_OK, L"axxb\n"},
|
|
|
|
{{L"string", L"match", L"a??b", L"axxb", nullptr}, STATUS_CMD_OK, L"axxb\n"},
|
|
|
|
{{L"string", L"match", L"-i", L"a??B", L"axxb", nullptr}, STATUS_CMD_OK, L"axxb\n"},
|
|
|
|
{{L"string", L"match", L"-i", L"a??b", L"Axxb", nullptr}, STATUS_CMD_OK, L"Axxb\n"},
|
|
|
|
{{L"string", L"match", L"a*", L"axxb", nullptr}, STATUS_CMD_OK, L"axxb\n"},
|
|
|
|
{{L"string", L"match", L"*a", L"xxa", nullptr}, STATUS_CMD_OK, L"xxa\n"},
|
|
|
|
{{L"string", L"match", L"*a*", L"axa", nullptr}, STATUS_CMD_OK, L"axa\n"},
|
|
|
|
{{L"string", L"match", L"*a*", L"xax", nullptr}, STATUS_CMD_OK, L"xax\n"},
|
|
|
|
{{L"string", L"match", L"*a*", L"bxa", nullptr}, STATUS_CMD_OK, L"bxa\n"},
|
|
|
|
{{L"string", L"match", L"*a", L"a", nullptr}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"match", L"a*", L"a", nullptr}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"match", L"a*b*c", L"axxbyyc", nullptr}, STATUS_CMD_OK, L"axxbyyc\n"},
|
|
|
|
{{L"string", L"match", L"\\*", L"*", nullptr}, STATUS_CMD_OK, L"*\n"},
|
|
|
|
{{L"string", L"match", L"a*\\", L"abc\\", nullptr}, STATUS_CMD_OK, L"abc\\\n"},
|
|
|
|
{{L"string", L"match", L"a*\\?", L"abc?", nullptr}, STATUS_CMD_OK, L"abc?\n"},
|
|
|
|
|
|
|
|
{{L"string", L"match", L"?", L"", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"?", L"ab", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"??", L"a", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"?a", L"a", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"a?", L"a", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"a??B", L"axxb", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"a*b", L"axxbc", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"*b", L"bbba", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"0x[0-9a-fA-F][0-9a-fA-F]", L"0xbad", nullptr},
|
|
|
|
STATUS_CMD_ERROR,
|
|
|
|
L""},
|
|
|
|
|
|
|
|
{{L"string", L"match", L"-a", L"*", L"ab", L"cde", nullptr}, STATUS_CMD_OK, L"ab\ncde\n"},
|
|
|
|
{{L"string", L"match", L"*", L"ab", L"cde", nullptr}, STATUS_CMD_OK, L"ab\ncde\n"},
|
|
|
|
{{L"string", L"match", L"-n", L"*d*", L"cde", nullptr}, STATUS_CMD_OK, L"1 3\n"},
|
|
|
|
{{L"string", L"match", L"-n", L"*x*", L"cde", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"-q", L"a*", L"b", L"c", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"-q", L"a*", L"b", L"a", nullptr}, STATUS_CMD_OK, L""},
|
|
|
|
|
|
|
|
{{L"string", L"match", L"-r", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"match", L"-r", L"", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"-r", L"", L"", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"match", L"-r", L".", L"a", nullptr}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"match", L"-r", L".*", L"", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"a*b", L"b", nullptr}, STATUS_CMD_OK, L"b\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"a*b", L"aab", nullptr}, STATUS_CMD_OK, L"aab\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"-i", L"a*b", L"Aab", nullptr}, STATUS_CMD_OK, L"Aab\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"-a", L"a[bc]", L"abadac", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"ab\nac\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"a", L"xaxa", L"axax", nullptr}, STATUS_CMD_OK, L"a\na\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"-a", L"a", L"xaxa", L"axax", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"a\na\na\na\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"match", L"-r", L"a[bc]", L"abadac", nullptr}, STATUS_CMD_OK, L"ab\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"-q", L"a[bc]", L"abadac", nullptr}, STATUS_CMD_OK, L""},
|
|
|
|
{{L"string", L"match", L"-r", L"-q", L"a[bc]", L"ad", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"-r", L"(a+)b(c)", L"aabc", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"aabc\naa\nc\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"-a", L"(a)b(c)", L"abcabc", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2016-05-01 01:37:19 +00:00
|
|
|
L"abc\na\nc\nabc\na\nc\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"match", L"-r", L"(a)b(c)", L"abcabc", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"abc\na\nc\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"(a|(z))(bc)", L"abc", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"abc\na\nbc\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"-n", L"a", L"ada", L"dad", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"1 1\n2 1\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"match", L"-r", L"-n", L"-a", L"a", L"bacadae", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"2 1\n4 1\n6 1\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"match", L"-r", L"-n", L"(a).*(b)", L"a---b", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"1 5\n1 1\n5 1\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"match", L"-r", L"-n", L"(a)(b)", L"ab", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"1 2\n1 1\n2 1\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"match", L"-r", L"-n", L"(a)(b)", L"abab", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"1 2\n1 1\n2 1\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"match", L"-r", L"-n", L"-a", L"(a)(b)", L"abab", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2016-05-01 01:37:19 +00:00
|
|
|
L"1 2\n1 1\n2 1\n3 2\n3 1\n4 1\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"match", L"-r", L"*", L"", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"match", L"-r", L"-a", L"a*", L"b", nullptr}, STATUS_CMD_OK, L"\n\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"foo\\Kbar", L"foobar", nullptr}, STATUS_CMD_OK, L"bar\n"},
|
|
|
|
{{L"string", L"match", L"-r", L"(foo)\\Kbar", L"foobar", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"bar\nfoo\n"},
|
|
|
|
{{L"string", L"replace", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"replace", L"", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"replace", L"", L"", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"replace", L"", L"", L"", nullptr}, STATUS_CMD_ERROR, L"\n"},
|
|
|
|
{{L"string", L"replace", L"", L"", L" ", nullptr}, STATUS_CMD_ERROR, L" \n"},
|
|
|
|
{{L"string", L"replace", L"a", L"b", L"", nullptr}, STATUS_CMD_ERROR, L"\n"},
|
|
|
|
{{L"string", L"replace", L"a", L"b", L"a", nullptr}, STATUS_CMD_OK, L"b\n"},
|
|
|
|
{{L"string", L"replace", L"a", L"b", L"xax", nullptr}, STATUS_CMD_OK, L"xbx\n"},
|
|
|
|
{{L"string", L"replace", L"a", L"b", L"xax", L"axa", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"xbx\nbxa\n"},
|
|
|
|
{{L"string", L"replace", L"bar", L"x", L"red barn", nullptr}, STATUS_CMD_OK, L"red xn\n"},
|
|
|
|
{{L"string", L"replace", L"x", L"bar", L"red xn", nullptr}, STATUS_CMD_OK, L"red barn\n"},
|
|
|
|
{{L"string", L"replace", L"--", L"x", L"-", L"xyz", nullptr}, STATUS_CMD_OK, L"-yz\n"},
|
|
|
|
{{L"string", L"replace", L"--", L"y", L"-", L"xyz", nullptr}, STATUS_CMD_OK, L"x-z\n"},
|
|
|
|
{{L"string", L"replace", L"--", L"z", L"-", L"xyz", nullptr}, STATUS_CMD_OK, L"xy-\n"},
|
|
|
|
{{L"string", L"replace", L"-i", L"z", L"X", L"_Z_", nullptr}, STATUS_CMD_OK, L"_X_\n"},
|
|
|
|
{{L"string", L"replace", L"-a", L"a", L"A", L"aaa", nullptr}, STATUS_CMD_OK, L"AAA\n"},
|
|
|
|
{{L"string", L"replace", L"-i", L"a", L"z", L"AAA", nullptr}, STATUS_CMD_OK, L"zAA\n"},
|
|
|
|
{{L"string", L"replace", L"-q", L"x", L">x<", L"x", nullptr}, STATUS_CMD_OK, L""},
|
|
|
|
{{L"string", L"replace", L"-a", L"x", L"", L"xxx", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"replace", L"-a", L"***", L"_", L"*****", nullptr}, STATUS_CMD_OK, L"_**\n"},
|
|
|
|
{{L"string", L"replace", L"-a", L"***", L"***", L"******", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"******\n"},
|
|
|
|
{{L"string", L"replace", L"-a", L"a", L"b", L"xax", L"axa", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"xbx\nbxb\n"},
|
|
|
|
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"replace", L"-r", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"replace", L"-r", L"", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"replace", L"-r", L"", L"", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"replace", L"-r", L"", L"", L"", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"\n"}, // pcre2 behavior
|
|
|
|
{{L"string", L"replace", L"-r", L"", L"", L" ", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L" \n"}, // pcre2 behavior
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"replace", L"-r", L"a", L"b", L"", nullptr}, STATUS_CMD_ERROR, L"\n"},
|
|
|
|
{{L"string", L"replace", L"-r", L"a", L"b", L"a", nullptr}, STATUS_CMD_OK, L"b\n"},
|
|
|
|
{{L"string", L"replace", L"-r", L".", L"x", L"abc", nullptr}, STATUS_CMD_OK, L"xbc\n"},
|
|
|
|
{{L"string", L"replace", L"-r", L".", L"", L"abc", nullptr}, STATUS_CMD_OK, L"bc\n"},
|
|
|
|
{{L"string", L"replace", L"-r", L"(\\w)(\\w)", L"$2$1", L"ab", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"ba\n"},
|
|
|
|
{{L"string", L"replace", L"-r", L"(\\w)", L"$1$1", L"ab", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"aab\n"},
|
|
|
|
{{L"string", L"replace", L"-r", L"-a", L".", L"x", L"abc", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"xxx\n"},
|
|
|
|
{{L"string", L"replace", L"-r", L"-a", L"(\\w)", L"$1$1", L"ab", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"aabb\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"replace", L"-r", L"-a", L".", L"", L"abc", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"replace", L"-r", L"a", L"x", L"bc", L"cd", L"de", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_ERROR,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"bc\ncd\nde\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"replace", L"-r", L"a", L"x", L"aba", L"caa", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"xba\ncxa\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"replace", L"-r", L"-a", L"a", L"x", L"aba", L"caa", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"xbx\ncxx\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"replace", L"-r", L"-i", L"A", L"b", L"xax", nullptr},
|
|
|
|
STATUS_CMD_OK,
|
|
|
|
L"xbx\n"},
|
|
|
|
{{L"string", L"replace", L"-r", L"-i", L"[a-z]", L".", L"1A2B", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"1.2B\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"replace", L"-r", L"A", L"b", L"xax", nullptr}, STATUS_CMD_ERROR, L"xax\n"},
|
|
|
|
{{L"string", L"replace", L"-r", L"a", L"$1", L"a", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"replace", L"-r", L"(a)", L"$2", L"a", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"replace", L"-r", L"*", L".", L"a", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"replace", L"-ra", L"x", L"\\c", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"replace", L"-r", L"^(.)", L"\t$1", L"abc", L"x", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"\tabc\n\tx\n"},
|
|
|
|
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"split", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"split", L":", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"split", L".", L"www.ch.ic.ac.uk", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"www\nch\nic\nac\nuk\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"split", L"..", L"....", nullptr}, STATUS_CMD_OK, L"\n\n\n"},
|
|
|
|
{{L"string", L"split", L"-m", L"x", L"..", L"....", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"split", L"-m1", L"..", L"....", nullptr}, STATUS_CMD_OK, L"\n..\n"},
|
|
|
|
{{L"string", L"split", L"-m0", L"/", L"/usr/local/bin/fish", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_ERROR,
|
2016-05-01 01:37:19 +00:00
|
|
|
L"/usr/local/bin/fish\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"split", L"-m2", L":", L"a:b:c:d", L"e:f:g:h", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2016-05-01 01:37:19 +00:00
|
|
|
L"a\nb\nc:d\ne\nf\ng:h\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"split", L"-m1", L"-r", L"/", L"/usr/local/bin/fish", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2016-05-01 01:37:19 +00:00
|
|
|
L"/usr/local/bin\nfish\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"split", L"-r", L".", L"www.ch.ic.ac.uk", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"www\nch\nic\nac\nuk\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"split", L"--", L"--", L"a--b---c----d", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"a\nb\n-c\n\nd\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"split", L"-r", L"..", L"....", nullptr}, STATUS_CMD_OK, L"\n\n\n"},
|
|
|
|
{{L"string", L"split", L"-r", L"--", L"--", L"a--b---c----d", nullptr},
|
2017-05-04 07:18:02 +00:00
|
|
|
STATUS_CMD_OK,
|
2017-05-02 07:39:50 +00:00
|
|
|
L"a\nb-\nc\n\nd\n"},
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"split", L"", L"", nullptr}, STATUS_CMD_ERROR, L"\n"},
|
|
|
|
{{L"string", L"split", L"", L"a", nullptr}, STATUS_CMD_ERROR, L"a\n"},
|
|
|
|
{{L"string", L"split", L"", L"ab", nullptr}, STATUS_CMD_OK, L"a\nb\n"},
|
|
|
|
{{L"string", L"split", L"", L"abc", nullptr}, STATUS_CMD_OK, L"a\nb\nc\n"},
|
|
|
|
{{L"string", L"split", L"-m1", L"", L"abc", nullptr}, STATUS_CMD_OK, L"a\nbc\n"},
|
|
|
|
{{L"string", L"split", L"-r", L"", L"", nullptr}, STATUS_CMD_ERROR, L"\n"},
|
|
|
|
{{L"string", L"split", L"-r", L"", L"a", nullptr}, STATUS_CMD_ERROR, L"a\n"},
|
|
|
|
{{L"string", L"split", L"-r", L"", L"ab", nullptr}, STATUS_CMD_OK, L"a\nb\n"},
|
|
|
|
{{L"string", L"split", L"-r", L"", L"abc", nullptr}, STATUS_CMD_OK, L"a\nb\nc\n"},
|
|
|
|
{{L"string", L"split", L"-r", L"-m1", L"", L"abc", nullptr}, STATUS_CMD_OK, L"ab\nc\n"},
|
|
|
|
{{L"string", L"split", L"-q", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"split", L"-q", L":", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"split", L"-q", L"x", L"axbxc", nullptr}, STATUS_CMD_OK, L""},
|
|
|
|
|
|
|
|
{{L"string", L"sub", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"sub", L"abcde", nullptr}, STATUS_CMD_OK, L"abcde\n"},
|
|
|
|
{{L"string", L"sub", L"-l", L"x", L"abcde", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"sub", L"-s", L"x", L"abcde", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"sub", L"-l0", L"abcde", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"sub", L"-l2", L"abcde", nullptr}, STATUS_CMD_OK, L"ab\n"},
|
|
|
|
{{L"string", L"sub", L"-l5", L"abcde", nullptr}, STATUS_CMD_OK, L"abcde\n"},
|
|
|
|
{{L"string", L"sub", L"-l6", L"abcde", nullptr}, STATUS_CMD_OK, L"abcde\n"},
|
|
|
|
{{L"string", L"sub", L"-l-1", L"abcde", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"sub", L"-s0", L"abcde", nullptr}, STATUS_INVALID_ARGS, L""},
|
|
|
|
{{L"string", L"sub", L"-s1", L"abcde", nullptr}, STATUS_CMD_OK, L"abcde\n"},
|
|
|
|
{{L"string", L"sub", L"-s5", L"abcde", nullptr}, STATUS_CMD_OK, L"e\n"},
|
|
|
|
{{L"string", L"sub", L"-s6", L"abcde", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"sub", L"-s-1", L"abcde", nullptr}, STATUS_CMD_OK, L"e\n"},
|
|
|
|
{{L"string", L"sub", L"-s-5", L"abcde", nullptr}, STATUS_CMD_OK, L"abcde\n"},
|
|
|
|
{{L"string", L"sub", L"-s-6", L"abcde", nullptr}, STATUS_CMD_OK, L"abcde\n"},
|
|
|
|
{{L"string", L"sub", L"-s1", L"-l0", L"abcde", nullptr}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"sub", L"-s1", L"-l1", L"abcde", nullptr}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"sub", L"-s2", L"-l2", L"abcde", nullptr}, STATUS_CMD_OK, L"bc\n"},
|
|
|
|
{{L"string", L"sub", L"-s-1", L"-l1", L"abcde", nullptr}, STATUS_CMD_OK, L"e\n"},
|
|
|
|
{{L"string", L"sub", L"-s-1", L"-l2", L"abcde", nullptr}, STATUS_CMD_OK, L"e\n"},
|
|
|
|
{{L"string", L"sub", L"-s-3", L"-l2", L"abcde", nullptr}, STATUS_CMD_OK, L"cd\n"},
|
|
|
|
{{L"string", L"sub", L"-s-3", L"-l4", L"abcde", nullptr}, STATUS_CMD_OK, L"cde\n"},
|
|
|
|
{{L"string", L"sub", L"-q", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"sub", L"-q", L"abcde", nullptr}, STATUS_CMD_OK, L""},
|
|
|
|
|
|
|
|
{{L"string", L"trim", nullptr}, STATUS_CMD_ERROR, L""},
|
2017-05-04 07:18:02 +00:00
|
|
|
{{L"string", L"trim", L""}, STATUS_CMD_ERROR, L"\n"},
|
|
|
|
{{L"string", L"trim", L" "}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"trim", L" \f\n\r\t"}, STATUS_CMD_OK, L"\n"},
|
|
|
|
{{L"string", L"trim", L" a"}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"trim", L"a "}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"trim", L" a "}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"trim", L"-l", L" a"}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"trim", L"-l", L"a "}, STATUS_CMD_ERROR, L"a \n"},
|
|
|
|
{{L"string", L"trim", L"-l", L" a "}, STATUS_CMD_OK, L"a \n"},
|
|
|
|
{{L"string", L"trim", L"-r", L" a"}, STATUS_CMD_ERROR, L" a\n"},
|
|
|
|
{{L"string", L"trim", L"-r", L"a "}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"trim", L"-r", L" a "}, STATUS_CMD_OK, L" a\n"},
|
|
|
|
{{L"string", L"trim", L"-c", L".", L" a"}, STATUS_CMD_ERROR, L" a\n"},
|
|
|
|
{{L"string", L"trim", L"-c", L".", L"a "}, STATUS_CMD_ERROR, L"a \n"},
|
|
|
|
{{L"string", L"trim", L"-c", L".", L" a "}, STATUS_CMD_ERROR, L" a \n"},
|
|
|
|
{{L"string", L"trim", L"-c", L".", L".a"}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"trim", L"-c", L".", L"a."}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"trim", L"-c", L".", L".a."}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"trim", L"-c", L"\\/", L"/a\\"}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"trim", L"-c", L"\\/", L"a/"}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"trim", L"-c", L"\\/", L"\\a/"}, STATUS_CMD_OK, L"a\n"},
|
2021-12-30 19:24:56 +00:00
|
|
|
{{L"string", L"trim", L"-c", L"", L".a."}, STATUS_CMD_ERROR, L".a.\n"}
|
|
|
|
};
|
2018-09-28 02:28:39 +00:00
|
|
|
|
2018-05-06 02:11:57 +00:00
|
|
|
for (const auto &t : string_tests) {
|
|
|
|
run_one_string_test(t.argv, t.expected_rc, t.expected_out);
|
|
|
|
}
|
2015-09-12 19:59:40 +00:00
|
|
|
|
2023-02-03 15:34:29 +00:00
|
|
|
bool saved_flag = feature_test(feature_flag_t::qmark_noglob);
|
2018-05-06 02:11:57 +00:00
|
|
|
const struct string_test qmark_noglob_tests[] = {
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"match", L"a*b?c", L"axxb?c", nullptr}, STATUS_CMD_OK, L"axxb?c\n"},
|
|
|
|
{{L"string", L"match", L"*?", L"a", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"*?", L"ab", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"?*", L"a", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"?*", L"ab", nullptr}, STATUS_CMD_ERROR, L""},
|
|
|
|
{{L"string", L"match", L"a*\\?", L"abc?", nullptr}, STATUS_CMD_ERROR, L""}};
|
2023-02-03 15:34:29 +00:00
|
|
|
mutable_fish_features()->set(feature_flag_t::qmark_noglob, true);
|
2018-05-06 02:11:57 +00:00
|
|
|
for (const auto &t : qmark_noglob_tests) {
|
|
|
|
run_one_string_test(t.argv, t.expected_rc, t.expected_out);
|
|
|
|
}
|
|
|
|
|
|
|
|
const struct string_test qmark_glob_tests[] = {
|
2021-08-17 22:57:52 +00:00
|
|
|
{{L"string", L"match", L"a*b?c", L"axxbyc", nullptr}, STATUS_CMD_OK, L"axxbyc\n"},
|
|
|
|
{{L"string", L"match", L"*?", L"a", nullptr}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"match", L"*?", L"ab", nullptr}, STATUS_CMD_OK, L"ab\n"},
|
|
|
|
{{L"string", L"match", L"?*", L"a", nullptr}, STATUS_CMD_OK, L"a\n"},
|
|
|
|
{{L"string", L"match", L"?*", L"ab", nullptr}, STATUS_CMD_OK, L"ab\n"},
|
|
|
|
{{L"string", L"match", L"a*\\?", L"abc?", nullptr}, STATUS_CMD_OK, L"abc?\n"}};
|
2023-02-03 15:34:29 +00:00
|
|
|
mutable_fish_features()->set(feature_flag_t::qmark_noglob, false);
|
2018-05-06 02:11:57 +00:00
|
|
|
for (const auto &t : qmark_glob_tests) {
|
|
|
|
run_one_string_test(t.argv, t.expected_rc, t.expected_out);
|
2015-09-12 19:59:40 +00:00
|
|
|
}
|
2023-02-03 15:34:29 +00:00
|
|
|
mutable_fish_features()->set(feature_flag_t::qmark_noglob, saved_flag);
|
2015-09-12 19:59:40 +00:00
|
|
|
}
|
|
|
|
|
2016-06-29 01:06:39 +00:00
|
|
|
/// Helper for test_timezone_env_vars().
|
|
|
|
long return_timezone_hour(time_t tstamp, const wchar_t *timezone) {
|
2018-09-14 07:36:26 +00:00
|
|
|
auto &vars = parser_t::principal_parser().vars();
|
2016-06-29 01:06:39 +00:00
|
|
|
struct tm ltime;
|
|
|
|
char ltime_str[3];
|
|
|
|
char *str_ptr;
|
2016-11-21 02:05:34 +00:00
|
|
|
size_t n;
|
2016-06-29 01:06:39 +00:00
|
|
|
|
2018-09-14 07:36:26 +00:00
|
|
|
vars.set_one(L"TZ", ENV_EXPORT, timezone);
|
2017-08-05 22:08:39 +00:00
|
|
|
|
2018-09-14 07:36:26 +00:00
|
|
|
const auto var = vars.get(L"TZ", ENV_DEFAULT);
|
2017-08-28 07:25:41 +00:00
|
|
|
(void)var;
|
2017-08-05 22:08:39 +00:00
|
|
|
|
2016-06-29 01:06:39 +00:00
|
|
|
localtime_r(&tstamp, <ime);
|
|
|
|
n = strftime(ltime_str, 3, "%H", <ime);
|
|
|
|
if (n != 2) {
|
|
|
|
err(L"strftime() returned %d, expected 2", n);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return strtol(ltime_str, &str_ptr, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Verify that setting special env vars have the expected effect on the current shell process.
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_timezone_env_vars() {
|
2016-06-29 01:06:39 +00:00
|
|
|
// Confirm changing the timezone affects fish's idea of the local time.
|
2021-08-17 22:57:52 +00:00
|
|
|
time_t tstamp = time(nullptr);
|
2016-06-29 01:06:39 +00:00
|
|
|
|
|
|
|
long first_tstamp = return_timezone_hour(tstamp, L"UTC-1");
|
|
|
|
long second_tstamp = return_timezone_hour(tstamp, L"UTC-2");
|
|
|
|
long delta = second_tstamp - first_tstamp;
|
|
|
|
if (delta != 1 && delta != -23) {
|
|
|
|
err(L"expected a one hour timezone delta got %ld", delta);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Verify that setting special env vars have the expected effect on the current shell process.
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_env_vars() {
|
2016-06-29 01:06:39 +00:00
|
|
|
test_timezone_env_vars();
|
|
|
|
// TODO: Add tests for the locale and ncurses vars.
|
2018-10-20 22:55:46 +00:00
|
|
|
|
|
|
|
env_var_t v1 = {L"abc", env_var_t::flag_export};
|
2023-04-18 22:19:10 +00:00
|
|
|
env_var_t v2 = {std::vector<wcstring>{L"abc"}, env_var_t::flag_export};
|
|
|
|
env_var_t v3 = {std::vector<wcstring>{L"abc"}, 0};
|
|
|
|
env_var_t v4 = {std::vector<wcstring>{L"abc", L"def"}, env_var_t::flag_export};
|
2019-05-05 10:09:25 +00:00
|
|
|
do_test(v1 == v2 && !(v1 != v2));
|
|
|
|
do_test(v1 != v3 && !(v1 == v3));
|
|
|
|
do_test(v1 != v4 && !(v1 == v4));
|
2016-06-29 01:06:39 +00:00
|
|
|
}
|
|
|
|
|
2019-05-10 01:38:18 +00:00
|
|
|
static void test_env_snapshot() {
|
|
|
|
if (system("mkdir -p test/fish_env_snapshot_test/")) err(L"mkdir failed");
|
|
|
|
bool pushed = pushd("test/fish_env_snapshot_test");
|
|
|
|
do_test(pushed);
|
|
|
|
auto &vars = parser_t::principal_parser().vars();
|
|
|
|
vars.push(true);
|
|
|
|
wcstring before_pwd = vars.get(L"PWD")->as_string();
|
|
|
|
vars.set(L"test_env_snapshot_var", 0, {L"before"});
|
|
|
|
const auto snapshot = vars.snapshot();
|
|
|
|
vars.set(L"PWD", 0, {L"/newdir"});
|
|
|
|
vars.set(L"test_env_snapshot_var", 0, {L"after"});
|
|
|
|
vars.set(L"test_env_snapshot_var_2", 0, {L"after"});
|
|
|
|
|
|
|
|
// vars should be unaffected by the snapshot
|
|
|
|
do_test(vars.get(L"PWD")->as_string() == L"/newdir");
|
|
|
|
do_test(vars.get(L"test_env_snapshot_var")->as_string() == L"after");
|
|
|
|
do_test(vars.get(L"test_env_snapshot_var_2")->as_string() == L"after");
|
|
|
|
|
|
|
|
// snapshot should have old values of vars
|
|
|
|
do_test(snapshot->get(L"PWD")->as_string() == before_pwd);
|
|
|
|
do_test(snapshot->get(L"test_env_snapshot_var")->as_string() == L"before");
|
|
|
|
do_test(snapshot->get(L"test_env_snapshot_var_2") == none());
|
|
|
|
|
|
|
|
// snapshots see global var changes except for perproc like PWD
|
|
|
|
vars.set(L"test_env_snapshot_var_3", ENV_GLOBAL, {L"reallyglobal"});
|
|
|
|
do_test(vars.get(L"test_env_snapshot_var_3")->as_string() == L"reallyglobal");
|
|
|
|
do_test(snapshot->get(L"test_env_snapshot_var_3")->as_string() == L"reallyglobal");
|
|
|
|
|
|
|
|
vars.pop();
|
|
|
|
popd();
|
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
static void test_illegal_command_exit_code() {
|
2016-12-03 16:15:29 +00:00
|
|
|
say(L"Testing illegal command exit code");
|
|
|
|
|
2017-04-09 04:05:18 +00:00
|
|
|
// We need to be in an empty directory so that none of the wildcards match a file that might be
|
|
|
|
// in the fish source tree. In particular we need to ensure that "?" doesn't match a file
|
|
|
|
// named by a single character. See issue #3852.
|
|
|
|
if (!pushd("test/temp")) return;
|
|
|
|
|
2016-12-03 16:15:29 +00:00
|
|
|
struct command_result_tuple_t {
|
|
|
|
const wchar_t *txt;
|
|
|
|
int result;
|
|
|
|
};
|
|
|
|
|
|
|
|
const command_result_tuple_t tests[] = {
|
2018-08-26 08:41:45 +00:00
|
|
|
{L"echo -n", STATUS_CMD_OK},
|
|
|
|
{L"pwd", STATUS_CMD_OK},
|
|
|
|
{L"UNMATCHABLE_WILDCARD*", STATUS_UNMATCHED_WILDCARD},
|
|
|
|
{L"UNMATCHABLE_WILDCARD**", STATUS_UNMATCHED_WILDCARD},
|
|
|
|
{L"?", STATUS_UNMATCHED_WILDCARD},
|
|
|
|
{L"abc?def", STATUS_UNMATCHED_WILDCARD},
|
2018-03-11 22:16:53 +00:00
|
|
|
};
|
2016-12-03 16:15:29 +00:00
|
|
|
|
|
|
|
const io_chain_t empty_ios;
|
|
|
|
parser_t &parser = parser_t::principal_parser();
|
|
|
|
|
2018-03-31 23:48:57 +00:00
|
|
|
for (const auto &test : tests) {
|
2019-12-23 00:27:03 +00:00
|
|
|
parser.eval(test.txt, empty_ios);
|
2016-12-03 16:15:29 +00:00
|
|
|
|
2019-05-12 21:00:44 +00:00
|
|
|
int exit_status = parser.get_last_status();
|
2018-03-31 23:48:57 +00:00
|
|
|
if (exit_status != test.result) {
|
2018-08-26 08:41:45 +00:00
|
|
|
err(L"command '%ls': expected exit code %d, got %d", test.txt, test.result,
|
2016-12-04 04:12:53 +00:00
|
|
|
exit_status);
|
2016-12-03 16:15:29 +00:00
|
|
|
}
|
|
|
|
}
|
2017-04-09 04:05:18 +00:00
|
|
|
|
|
|
|
popd();
|
2016-12-03 16:15:29 +00:00
|
|
|
}
|
|
|
|
|
2017-08-27 20:19:37 +00:00
|
|
|
void test_maybe() {
|
|
|
|
say(L"Testing maybe_t");
|
2022-10-08 16:56:38 +00:00
|
|
|
// maybe_t<T> bool conversion is only enabled for non-bool-convertible T types
|
|
|
|
do_test(!bool(maybe_t<wcstring>()));
|
2017-08-27 20:19:37 +00:00
|
|
|
maybe_t<int> m(3);
|
|
|
|
do_test(m.has_value());
|
|
|
|
do_test(m.value() == 3);
|
|
|
|
m.reset();
|
|
|
|
do_test(!m.has_value());
|
|
|
|
m = 123;
|
|
|
|
do_test(m.has_value());
|
|
|
|
do_test(m.has_value() && m.value() == 123);
|
|
|
|
m = maybe_t<int>();
|
|
|
|
do_test(!m.has_value());
|
|
|
|
m = maybe_t<int>(64);
|
|
|
|
do_test(m.has_value() && m.value() == 64);
|
|
|
|
m = 123;
|
|
|
|
do_test(m == maybe_t<int>(123));
|
|
|
|
do_test(m != maybe_t<int>());
|
|
|
|
do_test(maybe_t<int>() == none());
|
|
|
|
do_test(!maybe_t<int>(none()).has_value());
|
2022-10-08 16:56:38 +00:00
|
|
|
maybe_t<wcstring> n = none();
|
|
|
|
do_test(!bool(n));
|
2017-08-27 20:19:37 +00:00
|
|
|
|
2018-03-10 10:10:40 +00:00
|
|
|
maybe_t<std::string> m0 = none();
|
|
|
|
maybe_t<std::string> m3("hi");
|
|
|
|
maybe_t<std::string> m4 = m3;
|
|
|
|
do_test(m4 && *m4 == "hi");
|
|
|
|
maybe_t<std::string> m5 = m0;
|
|
|
|
do_test(!m5);
|
2018-04-01 23:10:59 +00:00
|
|
|
|
|
|
|
maybe_t<std::string> acquire_test("def");
|
|
|
|
do_test(acquire_test);
|
|
|
|
std::string res = acquire_test.acquire();
|
|
|
|
do_test(!acquire_test);
|
|
|
|
do_test(res == "def");
|
2019-03-17 19:47:24 +00:00
|
|
|
|
|
|
|
// maybe_t<T> should be copyable iff T is copyable.
|
|
|
|
using copyable = std::shared_ptr<int>;
|
|
|
|
using noncopyable = std::unique_ptr<int>;
|
|
|
|
do_test(std::is_copy_assignable<maybe_t<copyable>>::value == true);
|
|
|
|
do_test(std::is_copy_constructible<maybe_t<copyable>>::value == true);
|
|
|
|
do_test(std::is_copy_assignable<maybe_t<noncopyable>>::value == false);
|
|
|
|
do_test(std::is_copy_constructible<maybe_t<noncopyable>>::value == false);
|
|
|
|
|
2022-10-16 22:40:33 +00:00
|
|
|
// We can construct a maybe_t from noncopyable things.
|
|
|
|
maybe_t<noncopyable> nmt{make_unique<int>(42)};
|
|
|
|
do_test(**nmt == 42);
|
|
|
|
|
2019-03-17 19:47:24 +00:00
|
|
|
maybe_t<std::string> c1{"abc"};
|
|
|
|
maybe_t<std::string> c2 = c1;
|
|
|
|
do_test(c1.value() == "abc");
|
|
|
|
do_test(c2.value() == "abc");
|
|
|
|
c2 = c1;
|
|
|
|
do_test(c1.value() == "abc");
|
|
|
|
do_test(c2.value() == "abc");
|
2022-10-16 22:40:33 +00:00
|
|
|
|
|
|
|
do_test(c2.value_or("derp") == "abc");
|
|
|
|
do_test(c2.value_or("derp") == "abc");
|
|
|
|
c2.reset();
|
|
|
|
do_test(c2.value_or("derp") == "derp");
|
2017-08-27 20:19:37 +00:00
|
|
|
}
|
|
|
|
|
2018-02-04 23:43:03 +00:00
|
|
|
void test_layout_cache() {
|
2018-02-04 23:03:50 +00:00
|
|
|
layout_cache_t seqs;
|
2018-02-04 23:43:03 +00:00
|
|
|
|
|
|
|
// Verify escape code cache.
|
2018-02-04 23:03:50 +00:00
|
|
|
do_test(seqs.find_escape_code(L"abc") == 0);
|
|
|
|
seqs.add_escape_code(L"abc");
|
|
|
|
seqs.add_escape_code(L"abc");
|
|
|
|
do_test(seqs.esc_cache_size() == 1);
|
|
|
|
do_test(seqs.find_escape_code(L"abc") == 3);
|
|
|
|
do_test(seqs.find_escape_code(L"abcd") == 3);
|
|
|
|
do_test(seqs.find_escape_code(L"abcde") == 3);
|
|
|
|
do_test(seqs.find_escape_code(L"xabcde") == 0);
|
|
|
|
seqs.add_escape_code(L"ac");
|
|
|
|
do_test(seqs.find_escape_code(L"abcd") == 3);
|
|
|
|
do_test(seqs.find_escape_code(L"acbd") == 2);
|
|
|
|
seqs.add_escape_code(L"wxyz");
|
|
|
|
do_test(seqs.find_escape_code(L"abc") == 3);
|
|
|
|
do_test(seqs.find_escape_code(L"abcd") == 3);
|
|
|
|
do_test(seqs.find_escape_code(L"wxyz123") == 4);
|
|
|
|
do_test(seqs.find_escape_code(L"qwxyz123") == 0);
|
|
|
|
do_test(seqs.esc_cache_size() == 3);
|
2017-09-01 21:33:59 +00:00
|
|
|
seqs.clear();
|
2018-02-04 23:03:50 +00:00
|
|
|
do_test(seqs.esc_cache_size() == 0);
|
|
|
|
do_test(seqs.find_escape_code(L"abcd") == 0);
|
2018-02-04 23:43:03 +00:00
|
|
|
|
2020-05-31 00:10:44 +00:00
|
|
|
auto huge = std::numeric_limits<size_t>::max();
|
|
|
|
|
2018-02-04 23:43:03 +00:00
|
|
|
// Verify prompt layout cache.
|
|
|
|
for (size_t i = 0; i < layout_cache_t::prompt_cache_max_size; i++) {
|
|
|
|
wcstring input = std::to_wstring(i);
|
|
|
|
do_test(!seqs.find_prompt_layout(input));
|
2020-11-12 18:52:50 +00:00
|
|
|
seqs.add_prompt_layout({input, huge, input, {{}, i, 0}});
|
|
|
|
do_test(seqs.find_prompt_layout(input)->layout.max_line_width == i);
|
2018-02-04 23:43:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
size_t expected_evictee = 3;
|
|
|
|
for (size_t i = 0; i < layout_cache_t::prompt_cache_max_size; i++) {
|
|
|
|
if (i != expected_evictee)
|
2020-11-12 18:52:50 +00:00
|
|
|
do_test(seqs.find_prompt_layout(std::to_wstring(i))->layout.max_line_width == i);
|
2018-02-04 23:43:03 +00:00
|
|
|
}
|
|
|
|
|
2020-11-12 18:52:50 +00:00
|
|
|
seqs.add_prompt_layout({L"whatever", huge, L"whatever", {{}, 100, 0}});
|
2018-02-04 23:43:03 +00:00
|
|
|
do_test(!seqs.find_prompt_layout(std::to_wstring(expected_evictee)));
|
2020-11-12 18:52:50 +00:00
|
|
|
do_test(seqs.find_prompt_layout(L"whatever", huge)->layout.max_line_width == 100);
|
2020-05-31 00:10:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void test_prompt_truncation() {
|
|
|
|
layout_cache_t cache;
|
|
|
|
wcstring trunc;
|
|
|
|
prompt_layout_t layout;
|
|
|
|
|
|
|
|
/// Helper to return 'layout' formatted as a string for easy comparison.
|
|
|
|
auto format_layout = [&] {
|
2021-08-17 22:45:42 +00:00
|
|
|
wcstring line_breaks;
|
2020-11-12 18:52:50 +00:00
|
|
|
bool first = true;
|
|
|
|
for (const size_t line_break : layout.line_breaks) {
|
|
|
|
if (!first) {
|
|
|
|
line_breaks.push_back(L',');
|
|
|
|
}
|
|
|
|
line_breaks.append(format_string(L"%lu", (unsigned long)line_break));
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
return format_string(L"[%ls],%lu,%lu", line_breaks.c_str(),
|
2020-05-31 00:10:44 +00:00
|
|
|
(unsigned long)layout.max_line_width,
|
|
|
|
(unsigned long)layout.last_line_width);
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Join some strings with newline.
|
|
|
|
auto join = [](std::initializer_list<wcstring> vals) { return join_strings(vals, L'\n'); };
|
|
|
|
|
|
|
|
wcstring ellipsis = {get_ellipsis_char()};
|
|
|
|
|
|
|
|
// No truncation.
|
|
|
|
layout = cache.calc_prompt_layout(L"abcd", &trunc);
|
2020-11-12 18:52:50 +00:00
|
|
|
do_test(format_layout() == L"[],4,4");
|
2020-05-31 00:10:44 +00:00
|
|
|
do_test(trunc == L"abcd");
|
|
|
|
|
2020-11-12 18:52:50 +00:00
|
|
|
// Line break calculation.
|
|
|
|
layout = cache.calc_prompt_layout(join({
|
|
|
|
L"0123456789ABCDEF", //
|
|
|
|
L"012345", //
|
|
|
|
L"0123456789abcdef", //
|
|
|
|
L"xyz" //
|
|
|
|
}),
|
|
|
|
&trunc, 80);
|
|
|
|
do_test(format_layout() == L"[16,23,40],16,3");
|
|
|
|
|
2020-05-31 00:10:44 +00:00
|
|
|
// Basic truncation.
|
|
|
|
layout = cache.calc_prompt_layout(L"0123456789ABCDEF", &trunc, 8);
|
2020-11-12 18:52:50 +00:00
|
|
|
do_test(format_layout() == L"[],8,8");
|
2020-05-31 00:10:44 +00:00
|
|
|
do_test(trunc == ellipsis + L"9ABCDEF");
|
|
|
|
|
|
|
|
// Multiline truncation.
|
|
|
|
layout = cache.calc_prompt_layout(join({
|
|
|
|
L"0123456789ABCDEF", //
|
|
|
|
L"012345", //
|
|
|
|
L"0123456789abcdef", //
|
|
|
|
L"xyz" //
|
|
|
|
}),
|
|
|
|
&trunc, 8);
|
2020-11-12 18:52:50 +00:00
|
|
|
do_test(format_layout() == L"[8,15,24],8,3");
|
2020-05-31 00:10:44 +00:00
|
|
|
do_test(trunc == join({ellipsis + L"9ABCDEF", L"012345", ellipsis + L"9abcdef", L"xyz"}));
|
|
|
|
|
|
|
|
// Escape sequences are not truncated.
|
|
|
|
layout =
|
|
|
|
cache.calc_prompt_layout(L"\x1B]50;CurrentDir=test/foo\x07NOT_PART_OF_SEQUENCE", &trunc, 4);
|
2020-11-12 18:52:50 +00:00
|
|
|
do_test(format_layout() == L"[],4,4");
|
2020-05-31 00:10:44 +00:00
|
|
|
do_test(trunc == ellipsis + L"\x1B]50;CurrentDir=test/foo\x07NCE");
|
|
|
|
|
|
|
|
// Newlines in escape sequences are skipped.
|
|
|
|
layout = cache.calc_prompt_layout(L"\x1B]50;CurrentDir=\ntest/foo\x07NOT_PART_OF_SEQUENCE",
|
|
|
|
&trunc, 4);
|
2020-11-12 18:52:50 +00:00
|
|
|
do_test(format_layout() == L"[],4,4");
|
2020-05-31 00:10:44 +00:00
|
|
|
do_test(trunc == ellipsis + L"\x1B]50;CurrentDir=\ntest/foo\x07NCE");
|
|
|
|
|
|
|
|
// We will truncate down to one character if we have to.
|
|
|
|
layout = cache.calc_prompt_layout(L"Yay", &trunc, 1);
|
2020-11-12 18:52:50 +00:00
|
|
|
do_test(format_layout() == L"[],1,1");
|
2020-05-31 00:10:44 +00:00
|
|
|
do_test(trunc == ellipsis);
|
2017-09-01 21:33:59 +00:00
|
|
|
}
|
|
|
|
|
2018-09-17 00:55:15 +00:00
|
|
|
void test_normalize_path() {
|
|
|
|
say(L"Testing path normalization");
|
|
|
|
do_test(normalize_path(L"") == L".");
|
|
|
|
do_test(normalize_path(L"..") == L"..");
|
|
|
|
do_test(normalize_path(L"./") == L".");
|
2018-10-13 05:15:16 +00:00
|
|
|
do_test(normalize_path(L"./.") == L".");
|
|
|
|
do_test(normalize_path(L"/") == L"/");
|
|
|
|
do_test(normalize_path(L"//") == L"//");
|
|
|
|
do_test(normalize_path(L"///") == L"/");
|
|
|
|
do_test(normalize_path(L"////") == L"/");
|
|
|
|
do_test(normalize_path(L"/.///") == L"/");
|
|
|
|
do_test(normalize_path(L".//") == L".");
|
|
|
|
do_test(normalize_path(L"/.//../") == L"/");
|
|
|
|
do_test(normalize_path(L"////abc") == L"/abc");
|
2018-09-17 00:55:15 +00:00
|
|
|
do_test(normalize_path(L"/abc") == L"/abc");
|
|
|
|
do_test(normalize_path(L"/abc/") == L"/abc");
|
|
|
|
do_test(normalize_path(L"/abc/..def/") == L"/abc/..def");
|
|
|
|
do_test(normalize_path(L"//abc/../def/") == L"//def");
|
|
|
|
do_test(normalize_path(L"abc/../abc/../abc/../abc") == L"abc");
|
|
|
|
do_test(normalize_path(L"../../") == L"../..");
|
|
|
|
do_test(normalize_path(L"foo/./bar") == L"foo/bar");
|
2018-10-13 05:15:16 +00:00
|
|
|
do_test(normalize_path(L"foo/../") == L".");
|
|
|
|
do_test(normalize_path(L"foo/../foo") == L"foo");
|
|
|
|
do_test(normalize_path(L"foo/../foo/") == L"foo");
|
2018-09-17 00:55:15 +00:00
|
|
|
do_test(normalize_path(L"foo/././bar/.././baz") == L"foo/baz");
|
2018-11-18 02:02:28 +00:00
|
|
|
|
|
|
|
do_test(path_normalize_for_cd(L"/", L"..") == L"/..");
|
|
|
|
do_test(path_normalize_for_cd(L"/abc/", L"..") == L"/");
|
|
|
|
do_test(path_normalize_for_cd(L"/abc/def/", L"..") == L"/abc");
|
|
|
|
do_test(path_normalize_for_cd(L"/abc/def/", L"../..") == L"/");
|
|
|
|
do_test(path_normalize_for_cd(L"/abc///def/", L"../..") == L"/");
|
|
|
|
do_test(path_normalize_for_cd(L"/abc///def/", L"../..") == L"/");
|
|
|
|
do_test(path_normalize_for_cd(L"/abc///def///", L"../..") == L"/");
|
|
|
|
do_test(path_normalize_for_cd(L"/abc///def///", L"..") == L"/abc");
|
|
|
|
do_test(path_normalize_for_cd(L"/abc///def///", L"..") == L"/abc");
|
|
|
|
do_test(path_normalize_for_cd(L"/abc/def/", L"./././/..") == L"/abc");
|
|
|
|
do_test(path_normalize_for_cd(L"/abc/def/", L"../../../") == L"/../");
|
|
|
|
do_test(path_normalize_for_cd(L"/abc/def/", L"../ghi/..") == L"/abc/ghi/..");
|
2018-09-17 00:55:15 +00:00
|
|
|
}
|
|
|
|
|
2021-03-21 22:26:51 +00:00
|
|
|
void test_dirname_basename() {
|
|
|
|
say(L"Testing wdirname and wbasename");
|
|
|
|
const struct testcase_t {
|
|
|
|
const wchar_t *path;
|
|
|
|
const wchar_t *dir;
|
|
|
|
const wchar_t *base;
|
|
|
|
} testcases[] = {
|
|
|
|
{L"", L".", L"."},
|
|
|
|
{L"foo//", L".", L"foo"},
|
|
|
|
{L"foo//////", L".", L"foo"},
|
|
|
|
{L"/////foo", L"/", L"foo"},
|
|
|
|
{L"/////foo", L"/", L"foo"},
|
|
|
|
{L"//foo/////bar", L"//foo", L"bar"},
|
|
|
|
{L"foo/////bar", L"foo", L"bar"},
|
|
|
|
|
|
|
|
// Examples given in XPG4.2.
|
|
|
|
{L"/usr/lib", L"/usr", L"lib"},
|
|
|
|
{L"usr", L".", L"usr"},
|
|
|
|
{L"/", L"/", L"/"},
|
|
|
|
{L".", L".", L"."},
|
|
|
|
{L"..", L".", L".."},
|
|
|
|
};
|
|
|
|
for (const auto &tc : testcases) {
|
|
|
|
wcstring dir = wdirname(tc.path);
|
|
|
|
if (dir != tc.dir) {
|
|
|
|
err(L"Wrong dirname for \"%ls\": expected \"%ls\", got \"%ls\"", tc.path, tc.dir,
|
|
|
|
dir.c_str());
|
|
|
|
}
|
|
|
|
wcstring base = wbasename(tc.path);
|
|
|
|
if (base != tc.base) {
|
|
|
|
err(L"Wrong basename for \"%ls\": expected \"%ls\", got \"%ls\"", tc.path, tc.base,
|
|
|
|
base.c_str());
|
|
|
|
}
|
|
|
|
}
|
2021-03-21 23:00:29 +00:00
|
|
|
// Ensures strings which greatly exceed PATH_MAX still work (#7837).
|
|
|
|
wcstring longpath = get_overlong_path();
|
|
|
|
wcstring longpath_dir = longpath.substr(0, longpath.rfind(L'/'));
|
|
|
|
do_test(wdirname(longpath) == longpath_dir);
|
|
|
|
do_test(wbasename(longpath) == L"overlong");
|
2021-03-21 22:26:51 +00:00
|
|
|
}
|
|
|
|
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
static void test_topic_monitor() {
|
|
|
|
say(L"Testing topic monitor");
|
2023-01-14 22:56:24 +00:00
|
|
|
auto monitor_box = new_topic_monitor();
|
|
|
|
topic_monitor_t &monitor = *monitor_box;
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
generation_list_t gens{};
|
|
|
|
constexpr auto t = topic_t::sigchld;
|
2020-08-07 02:00:53 +00:00
|
|
|
gens.sigchld = 0;
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
do_test(monitor.generation_for_topic(t) == 0);
|
2020-08-07 02:00:53 +00:00
|
|
|
auto changed = monitor.check(&gens, false /* wait */);
|
|
|
|
do_test(!changed);
|
|
|
|
do_test(gens.sigchld == 0);
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
|
|
|
|
monitor.post(t);
|
2020-08-07 02:00:53 +00:00
|
|
|
changed = monitor.check(&gens, true /* wait */);
|
|
|
|
do_test(changed);
|
|
|
|
do_test(gens.at(t) == 1);
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
do_test(monitor.generation_for_topic(t) == 1);
|
|
|
|
|
|
|
|
monitor.post(t);
|
|
|
|
do_test(monitor.generation_for_topic(t) == 2);
|
2020-08-07 02:00:53 +00:00
|
|
|
changed = monitor.check(&gens, true /* wait */);
|
|
|
|
do_test(changed);
|
|
|
|
do_test(gens.sigchld == 2);
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void test_topic_monitor_torture() {
|
|
|
|
say(L"Torture-testing topic monitor");
|
2023-01-14 22:56:24 +00:00
|
|
|
auto monitor_box = new_topic_monitor();
|
|
|
|
topic_monitor_t &monitor = *monitor_box;
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
const size_t thread_count = 64;
|
2019-02-15 04:55:43 +00:00
|
|
|
constexpr auto t1 = topic_t::sigchld;
|
|
|
|
constexpr auto t2 = topic_t::sighupint;
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
std::vector<generation_list_t> gens;
|
2023-01-14 22:56:24 +00:00
|
|
|
gens.resize(thread_count, invalid_generations());
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
std::atomic<uint32_t> post_count{};
|
|
|
|
for (auto &gen : gens) {
|
|
|
|
gen = monitor.current_generations();
|
|
|
|
post_count += 1;
|
2019-02-15 04:55:43 +00:00
|
|
|
monitor.post(t1);
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::atomic<uint32_t> completed{};
|
|
|
|
std::vector<std::thread> threads;
|
|
|
|
for (size_t i = 0; i < thread_count; i++) {
|
|
|
|
threads.emplace_back(
|
|
|
|
[&](size_t i) {
|
|
|
|
for (size_t j = 0; j < (1 << 11); j++) {
|
|
|
|
auto before = gens[i];
|
2020-08-07 02:00:53 +00:00
|
|
|
auto changed = monitor.check(&gens[i], true /* wait */);
|
2019-03-04 02:06:31 +00:00
|
|
|
(void)changed;
|
2020-08-07 02:00:53 +00:00
|
|
|
do_test(before.at(t1) < gens[i].at(t1));
|
|
|
|
do_test(gens[i].at(t1) <= post_count);
|
|
|
|
do_test(gens[i].at(t2) == 0);
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
}
|
|
|
|
auto amt = completed.fetch_add(1, std::memory_order_relaxed);
|
2019-03-04 02:06:31 +00:00
|
|
|
(void)amt;
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
},
|
|
|
|
i);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (completed.load(std::memory_order_relaxed) < thread_count) {
|
|
|
|
post_count += 1;
|
2019-02-15 04:55:43 +00:00
|
|
|
monitor.post(t1);
|
2021-02-08 22:06:47 +00:00
|
|
|
std::this_thread::yield();
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
}
|
|
|
|
for (auto &t : threads) t.join();
|
|
|
|
}
|
|
|
|
|
2021-02-03 04:30:52 +00:00
|
|
|
static void test_pipes() {
|
|
|
|
say(L"Testing pipes");
|
|
|
|
// Here we just test that each pipe has CLOEXEC set and is in the high range.
|
|
|
|
// Note pipe creation may fail due to fd exhaustion; don't fail in that case.
|
|
|
|
std::vector<autoclose_pipes_t> pipes;
|
2021-01-31 19:32:10 +00:00
|
|
|
for (int i = 0; i < 10; i++) {
|
2021-02-03 04:30:52 +00:00
|
|
|
if (auto pipe = make_autoclose_pipes()) {
|
|
|
|
pipes.push_back(pipe.acquire());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const auto &pipe : pipes) {
|
|
|
|
for (int fd : {pipe.read.fd(), pipe.write.fd()}) {
|
|
|
|
do_test(fd >= k_first_high_fd);
|
|
|
|
int flags = fcntl(fd, F_GETFD, 0);
|
|
|
|
do_test(flags >= 0);
|
|
|
|
do_test(bool(flags & FD_CLOEXEC));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-07 01:44:40 +00:00
|
|
|
static void test_fd_event_signaller() {
|
|
|
|
say(L"Testing fd event signaller");
|
|
|
|
fd_event_signaller_t sema;
|
|
|
|
do_test(!sema.try_consume());
|
|
|
|
do_test(!sema.poll());
|
|
|
|
|
|
|
|
// Post once.
|
|
|
|
sema.post();
|
|
|
|
do_test(sema.poll());
|
|
|
|
do_test(sema.poll());
|
|
|
|
do_test(sema.try_consume());
|
|
|
|
do_test(!sema.poll());
|
|
|
|
do_test(!sema.try_consume());
|
|
|
|
|
|
|
|
// Posts are coalesced.
|
|
|
|
sema.post();
|
|
|
|
sema.post();
|
|
|
|
sema.post();
|
|
|
|
do_test(sema.poll());
|
|
|
|
do_test(sema.poll());
|
|
|
|
do_test(sema.try_consume());
|
|
|
|
do_test(!sema.poll());
|
|
|
|
do_test(!sema.try_consume());
|
|
|
|
}
|
|
|
|
|
2021-04-09 19:08:56 +00:00
|
|
|
static void test_killring() {
|
|
|
|
say(L"Testing killring");
|
|
|
|
|
|
|
|
do_test(kill_entries().empty());
|
|
|
|
|
|
|
|
kill_add(L"a");
|
|
|
|
kill_add(L"b");
|
|
|
|
kill_add(L"c");
|
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
do_test((kill_entries() == std::vector<wcstring>{L"c", L"b", L"a"}));
|
2021-04-09 19:08:56 +00:00
|
|
|
|
|
|
|
do_test(kill_yank_rotate() == L"b");
|
2023-04-18 22:19:10 +00:00
|
|
|
do_test((kill_entries() == std::vector<wcstring>{L"b", L"a", L"c"}));
|
2021-04-09 19:08:56 +00:00
|
|
|
|
|
|
|
do_test(kill_yank_rotate() == L"a");
|
2023-04-18 22:19:10 +00:00
|
|
|
do_test((kill_entries() == std::vector<wcstring>{L"a", L"c", L"b"}));
|
2021-04-09 19:08:56 +00:00
|
|
|
|
|
|
|
kill_add(L"d");
|
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
do_test((kill_entries() == std::vector<wcstring>{L"d", L"a", L"c", L"b"}));
|
2021-04-09 19:08:56 +00:00
|
|
|
|
|
|
|
do_test(kill_yank_rotate() == L"a");
|
2023-04-18 22:19:10 +00:00
|
|
|
do_test((kill_entries() == std::vector<wcstring>{L"a", L"c", L"b", L"d"}));
|
2021-04-09 19:08:56 +00:00
|
|
|
}
|
|
|
|
|
2022-07-09 18:08:27 +00:00
|
|
|
namespace {
|
|
|
|
using namespace re;
|
|
|
|
|
|
|
|
// Basic tests for re, which wraps PCRE2.
|
|
|
|
static void test_re_errs() {
|
|
|
|
say(L"Testing re");
|
|
|
|
flags_t flags{};
|
|
|
|
re_error_t error{};
|
|
|
|
maybe_t<regex_t> re;
|
|
|
|
do_test(!regex_t::try_compile(L"abc[", flags, &error));
|
|
|
|
do_test(error.code != 0);
|
|
|
|
do_test(!error.message().empty());
|
|
|
|
|
|
|
|
error = re_error_t{};
|
|
|
|
do_test(!regex_t::try_compile(L"abc(", flags, &error).has_value());
|
|
|
|
do_test(error.code != 0);
|
|
|
|
do_test(!error.message().empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_re_basic() {
|
|
|
|
// Match a character twice.
|
|
|
|
using namespace re;
|
|
|
|
wcstring subject = L"AAbCCd11e";
|
|
|
|
auto substr_from_range = [&](maybe_t<match_range_t> r) {
|
|
|
|
do_test(r.has_value());
|
|
|
|
do_test(r->begin <= r->end);
|
|
|
|
do_test(r->end <= subject.size());
|
|
|
|
return subject.substr(r->begin, r->end - r->begin);
|
|
|
|
};
|
|
|
|
auto re = regex_t::try_compile(L"(.)\\1");
|
|
|
|
do_test(re.has_value());
|
|
|
|
auto md = re->prepare();
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> matches;
|
|
|
|
std::vector<wcstring> captures;
|
2022-07-09 18:08:27 +00:00
|
|
|
while (auto r = re->match(md, subject)) {
|
|
|
|
matches.push_back(substr_from_range(r));
|
|
|
|
captures.push_back(substr_from_range(re->group(md, 1)));
|
|
|
|
do_test(!re->group(md, 2));
|
|
|
|
}
|
|
|
|
do_test(join_strings(matches, L',') == L"AA,CC,11");
|
|
|
|
do_test(join_strings(captures, L',') == L"A,C,1");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_re_reset() {
|
|
|
|
using namespace re;
|
|
|
|
auto re = regex_t::try_compile(L"([0-9])");
|
|
|
|
wcstring s = L"012345";
|
|
|
|
auto md = re->prepare();
|
|
|
|
for (size_t idx = 0; idx < s.size(); idx++) {
|
|
|
|
md.reset();
|
|
|
|
for (size_t j = 0; j <= idx; j++) {
|
|
|
|
auto m = re->match(md, s);
|
|
|
|
match_range_t expected{j, j + 1};
|
|
|
|
do_test(m == expected);
|
|
|
|
do_test(re->group(md, 1) == expected);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_re_named() {
|
|
|
|
// Named capture groups.
|
|
|
|
using namespace re;
|
|
|
|
auto re = regex_t::try_compile(L"A(?<FOO>x+)?");
|
|
|
|
do_test(re->capture_group_count() == 1);
|
|
|
|
|
|
|
|
wcstring subject = L"AxxAAx";
|
|
|
|
auto md = re->prepare();
|
|
|
|
|
|
|
|
auto r = re->match(md, subject);
|
|
|
|
do_test((r == match_range_t{0, 3}));
|
|
|
|
do_test(re->substring_for_group(md, L"QQQ", subject) == none());
|
|
|
|
do_test(re->substring_for_group(md, L"FOO", subject) == L"xx");
|
|
|
|
|
|
|
|
r = re->match(md, subject);
|
|
|
|
do_test((r == match_range_t{3, 4}));
|
|
|
|
do_test(re->substring_for_group(md, L"QQQ", subject) == none());
|
|
|
|
do_test(re->substring_for_group(md, L"FOO", subject) == none());
|
|
|
|
|
|
|
|
r = re->match(md, subject);
|
|
|
|
do_test((r == match_range_t{4, 6}));
|
|
|
|
do_test(re->substring_for_group(md, L"QQQ", subject) == none());
|
|
|
|
do_test(re->substring_for_group(md, L"FOO", subject) == wcstring(L"x"));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_re_name_extraction() {
|
|
|
|
// Names of capture groups can be extracted.
|
|
|
|
using namespace re;
|
|
|
|
auto re = regex_t::try_compile(L"(?<FOO>dd)ff(?<BAR>cc)aaa(?<alpha>)ff(?<BETA>)");
|
|
|
|
do_test(re.has_value());
|
|
|
|
do_test(re->capture_group_count() == 4);
|
|
|
|
// PCRE2 returns these sorted.
|
|
|
|
do_test(join_strings(re->capture_group_names(), L',') == L"BAR,BETA,FOO,alpha");
|
|
|
|
|
|
|
|
// Mixed named and positional captures.
|
|
|
|
re = regex_t::try_compile(L"(abc)(?<FOO>def)(ghi)(?<BAR>jkl)");
|
|
|
|
do_test(re.has_value());
|
|
|
|
do_test(re->capture_group_count() == 4);
|
|
|
|
do_test(join_strings(re->capture_group_names(), L',') == L"BAR,FOO");
|
|
|
|
auto md = re->prepare();
|
|
|
|
const wcstring subject = L"abcdefghijkl";
|
|
|
|
auto m = re->match(md, subject);
|
|
|
|
do_test((m == match_range_t{0, 12}));
|
|
|
|
do_test((re->group(md, 1) == match_range_t{0, 3}));
|
|
|
|
do_test((re->group(md, 2) == match_range_t{3, 6}));
|
|
|
|
do_test((re->group(md, 3) == match_range_t{6, 9}));
|
|
|
|
do_test((re->group(md, 4) == match_range_t{9, 12}));
|
|
|
|
do_test(re->substring_for_group(md, L"FOO", subject) == wcstring(L"def"));
|
|
|
|
do_test(re->substring_for_group(md, L"BAR", subject) == wcstring(L"jkl"));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_re_substitute() {
|
|
|
|
// Names of capture groups can be extracted.
|
|
|
|
using namespace re;
|
|
|
|
auto re = regex_t::try_compile(L"[a-z]+(\\d+)");
|
|
|
|
do_test(re.has_value());
|
|
|
|
do_test(re->capture_group_count() == 1);
|
|
|
|
maybe_t<wcstring> res{};
|
|
|
|
int repl_count{};
|
|
|
|
sub_flags_t sflags{};
|
|
|
|
const wcstring subj = L"AAabc123ZZ AAabc123ZZ";
|
|
|
|
const wcstring repl = L"$1qqq";
|
|
|
|
res = re->substitute(subj, repl, sflags, 0, nullptr, &repl_count);
|
|
|
|
do_test(res && *res == L"AA123qqqZZ AAabc123ZZ");
|
|
|
|
do_test(repl_count == 1);
|
|
|
|
|
|
|
|
res = re->substitute(subj, repl, sflags, 5, nullptr, &repl_count);
|
|
|
|
do_test(res && *res == L"AAabc123ZZ AA123qqqZZ");
|
|
|
|
do_test(repl_count == 1);
|
|
|
|
|
|
|
|
sflags.global = true;
|
|
|
|
res = re->substitute(subj, repl, sflags, 0, nullptr, &repl_count);
|
|
|
|
do_test(res && *res == L"AA123qqqZZ AA123qqqZZ");
|
|
|
|
do_test(repl_count == 2);
|
|
|
|
|
|
|
|
sflags.extended = true;
|
|
|
|
res = re->substitute(subj, L"\\x21", sflags, 0, nullptr, &repl_count); // \x21 = !
|
|
|
|
do_test(res && *res == L"AA!ZZ AA!ZZ");
|
|
|
|
do_test(repl_count == 2);
|
|
|
|
|
|
|
|
// Test with a bad escape; \b is unsupported.
|
|
|
|
re_error_t error{};
|
|
|
|
res = re->substitute(subj, L"AAA\\bZZZ", sflags, 0, &error);
|
|
|
|
do_test(!res.has_value());
|
|
|
|
do_test(error.code == -57 /* PCRE2_ERROR_BADREPESCAPE */);
|
|
|
|
do_test(error.message() == L"bad escape sequence in replacement string");
|
|
|
|
do_test(error.offset == 5 /* the b */);
|
|
|
|
|
|
|
|
// Test a very long replacement as we used a fixed-size buffer.
|
|
|
|
sflags = sub_flags_t{};
|
|
|
|
sflags.global = true;
|
|
|
|
re = regex_t::try_compile(L"A");
|
|
|
|
res =
|
|
|
|
re->substitute(wcstring(4096, L'A'), wcstring(4096, L'X'), sflags, 0, nullptr, &repl_count);
|
|
|
|
do_test(res && *res == wcstring(4096 * 4096, L'X'));
|
|
|
|
do_test(repl_count == 4096);
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2022-12-09 21:31:19 +00:00
|
|
|
void test_wgetopt() {
|
|
|
|
// Regression test for a crash.
|
|
|
|
const wchar_t *const short_options = L"-a";
|
|
|
|
const struct woption long_options[] = {
|
|
|
|
{L"add", no_argument, 'a'},
|
|
|
|
{},
|
|
|
|
};
|
|
|
|
const wchar_t *argv[] = {L"abbr", L"--add", L"emacsnw", L"emacs", L"-nw", nullptr};
|
|
|
|
int argc = builtin_count_args(argv);
|
|
|
|
wgetopter_t w;
|
|
|
|
int opt;
|
|
|
|
int a_count = 0;
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> arguments;
|
2022-12-09 21:31:19 +00:00
|
|
|
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) {
|
|
|
|
switch (opt) {
|
|
|
|
case 'a': {
|
|
|
|
a_count += 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 1: {
|
|
|
|
// non-option argument
|
|
|
|
do_test(w.woptarg != nullptr);
|
|
|
|
arguments.push_back(w.woptarg);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case '?': {
|
|
|
|
// unrecognized option
|
|
|
|
if (argv[w.woptind - 1]) {
|
|
|
|
do_test(argv[w.woptind - 1] != nullptr);
|
|
|
|
arguments.push_back(argv[w.woptind - 1]);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
err(L"Unexpected option '%d'", opt);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
do_test(a_count == 1);
|
|
|
|
do_test(arguments.size() == 3);
|
|
|
|
do_test(join_strings(arguments, L' ') == L"emacsnw emacs -nw");
|
|
|
|
}
|
|
|
|
|
2023-01-14 22:56:24 +00:00
|
|
|
void test_rust_smoke() {
|
|
|
|
size_t x = rust::add(37, 5);
|
|
|
|
do_test(x == 42);
|
|
|
|
}
|
|
|
|
|
2023-01-15 22:56:04 +00:00
|
|
|
void test_rust_ffi() { rust::run_ffi_tests(); }
|
|
|
|
|
2021-07-31 00:51:42 +00:00
|
|
|
// typedef void (test_entry_point_t)();
|
|
|
|
using test_entry_point_t = void (*)();
|
|
|
|
struct test_t {
|
|
|
|
const char *group;
|
|
|
|
std::function<void()> test;
|
|
|
|
bool opt_in = false;
|
|
|
|
|
|
|
|
test_t(const char *group, test_entry_point_t test, bool opt_in = false)
|
|
|
|
: group(group), test(test), opt_in(opt_in) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct test_comparator_t {
|
|
|
|
// template<typename T=test_t>
|
|
|
|
int operator()(const test_t &lhs, const test_t &rhs) { return strcmp(lhs.group, rhs.group); }
|
|
|
|
};
|
|
|
|
|
|
|
|
// This magic string is required for CMake to pick up the list of tests
|
|
|
|
#define TEST_GROUP(x) x
|
|
|
|
static const test_t s_tests[]{
|
|
|
|
{TEST_GROUP("utility_functions"), test_utility_functions},
|
2022-09-25 19:04:33 +00:00
|
|
|
{TEST_GROUP("dir_iter"), test_dir_iter},
|
2021-07-31 00:51:42 +00:00
|
|
|
{TEST_GROUP("wwrite_to_fd"), test_wwrite_to_fd},
|
|
|
|
{TEST_GROUP("env_vars"), test_env_vars},
|
|
|
|
{TEST_GROUP("env"), test_env_snapshot},
|
|
|
|
{TEST_GROUP("str_to_num"), test_str_to_num},
|
|
|
|
{TEST_GROUP("enum"), test_enum_set},
|
|
|
|
{TEST_GROUP("enum"), test_enum_array},
|
|
|
|
{TEST_GROUP("highlighting"), test_highlighting},
|
|
|
|
{TEST_GROUP("new_parser_ll2"), test_new_parser_ll2},
|
|
|
|
{TEST_GROUP("new_parser_fuzzing"), test_new_parser_fuzzing},
|
|
|
|
{TEST_GROUP("new_parser_correctness"), test_new_parser_correctness},
|
|
|
|
{TEST_GROUP("new_parser_ad_hoc"), test_new_parser_ad_hoc},
|
|
|
|
{TEST_GROUP("new_parser_errors"), test_new_parser_errors},
|
|
|
|
{TEST_GROUP("error_messages"), test_error_messages},
|
|
|
|
{TEST_GROUP("escape"), test_unescape_sane},
|
|
|
|
{TEST_GROUP("escape"), test_escape_crazy},
|
|
|
|
{TEST_GROUP("format"), test_format},
|
|
|
|
{TEST_GROUP("convert"), test_convert},
|
|
|
|
{TEST_GROUP("convert"), test_convert_private_use},
|
|
|
|
{TEST_GROUP("convert_ascii"), test_convert_ascii},
|
|
|
|
{TEST_GROUP("perf_convert_ascii"), perf_convert_ascii, true},
|
|
|
|
{TEST_GROUP("convert_nulls"), test_convert_nulls},
|
|
|
|
{TEST_GROUP("tokenizer"), test_tokenizer},
|
|
|
|
{TEST_GROUP("iothread"), test_iothread},
|
|
|
|
{TEST_GROUP("pthread"), test_pthread},
|
|
|
|
{TEST_GROUP("debounce"), test_debounce},
|
|
|
|
{TEST_GROUP("debounce"), test_debounce_timeout},
|
|
|
|
{TEST_GROUP("parser"), test_parser},
|
|
|
|
{TEST_GROUP("cancellation"), test_cancellation},
|
|
|
|
{TEST_GROUP("utf8"), test_utf8},
|
|
|
|
{TEST_GROUP("escape_sequences"), test_escape_sequences},
|
|
|
|
{TEST_GROUP("lru"), test_lru},
|
|
|
|
{TEST_GROUP("expand"), test_expand},
|
|
|
|
{TEST_GROUP("expand"), test_expand_overflow},
|
|
|
|
{TEST_GROUP("abbreviations"), test_abbreviations},
|
|
|
|
{TEST_GROUP("wcstod"), test_wcstod},
|
|
|
|
{TEST_GROUP("dup2s"), test_dup2s},
|
|
|
|
{TEST_GROUP("dup2s"), test_dup2s_fd_for_target_fd},
|
|
|
|
{TEST_GROUP("pager_navigation"), test_pager_navigation},
|
|
|
|
{TEST_GROUP("pager_layout"), test_pager_layout},
|
|
|
|
{TEST_GROUP("word_motion"), test_word_motion},
|
|
|
|
{TEST_GROUP("is_potential_path"), test_is_potential_path},
|
|
|
|
{TEST_GROUP("colors"), test_colors},
|
2021-11-27 20:46:15 +00:00
|
|
|
{TEST_GROUP("wildcard"), test_wildcards},
|
2021-07-31 00:51:42 +00:00
|
|
|
{TEST_GROUP("complete"), test_complete},
|
|
|
|
{TEST_GROUP("autoload"), test_autoload},
|
|
|
|
{TEST_GROUP("input"), test_input},
|
|
|
|
{TEST_GROUP("undo"), test_undo},
|
|
|
|
{TEST_GROUP("universal"), test_universal},
|
|
|
|
{TEST_GROUP("universal"), test_universal_output},
|
|
|
|
{TEST_GROUP("universal"), test_universal_parsing},
|
|
|
|
{TEST_GROUP("universal"), test_universal_parsing_legacy},
|
|
|
|
{TEST_GROUP("universal"), test_universal_callbacks},
|
|
|
|
{TEST_GROUP("universal"), test_universal_formats},
|
|
|
|
{TEST_GROUP("universal"), test_universal_ok_to_save},
|
2022-01-09 22:26:33 +00:00
|
|
|
{TEST_GROUP("universal"), test_universal_notifiers},
|
2021-07-31 00:51:42 +00:00
|
|
|
{TEST_GROUP("completion_insertions"), test_completion_insertions},
|
|
|
|
{TEST_GROUP("autosuggestion_ignores"), test_autosuggestion_ignores},
|
|
|
|
{TEST_GROUP("autosuggestion_combining"), test_autosuggestion_combining},
|
|
|
|
{TEST_GROUP("autosuggest_suggest_special"), test_autosuggest_suggest_special},
|
|
|
|
{TEST_GROUP("history"), history_tests_t::test_history},
|
|
|
|
{TEST_GROUP("history_merge"), history_tests_t::test_history_merge},
|
|
|
|
{TEST_GROUP("history_paths"), history_tests_t::test_history_path_detection},
|
|
|
|
{TEST_GROUP("history_races"), history_tests_t::test_history_races},
|
|
|
|
{TEST_GROUP("history_formats"), history_tests_t::test_history_formats},
|
|
|
|
{TEST_GROUP("string"), test_string},
|
|
|
|
{TEST_GROUP("illegal_command_exit_code"), test_illegal_command_exit_code},
|
|
|
|
{TEST_GROUP("maybe"), test_maybe},
|
|
|
|
{TEST_GROUP("layout_cache"), test_layout_cache},
|
|
|
|
{TEST_GROUP("prompt"), test_prompt_truncation},
|
|
|
|
{TEST_GROUP("normalize"), test_normalize_path},
|
|
|
|
{TEST_GROUP("dirname"), test_dirname_basename},
|
|
|
|
{TEST_GROUP("topics"), test_topic_monitor},
|
|
|
|
{TEST_GROUP("topics"), test_topic_monitor_torture},
|
|
|
|
{TEST_GROUP("pipes"), test_pipes},
|
|
|
|
{TEST_GROUP("fd_event"), test_fd_event_signaller},
|
|
|
|
{TEST_GROUP("killring"), test_killring},
|
2022-07-09 18:08:27 +00:00
|
|
|
{TEST_GROUP("re"), test_re_errs},
|
|
|
|
{TEST_GROUP("re"), test_re_basic},
|
|
|
|
{TEST_GROUP("re"), test_re_reset},
|
|
|
|
{TEST_GROUP("re"), test_re_named},
|
|
|
|
{TEST_GROUP("re"), test_re_name_extraction},
|
|
|
|
{TEST_GROUP("re"), test_re_substitute},
|
2022-12-09 21:31:19 +00:00
|
|
|
{TEST_GROUP("wgetopt"), test_wgetopt},
|
2023-01-14 22:56:24 +00:00
|
|
|
{TEST_GROUP("rust_smoke"), test_rust_smoke},
|
2023-01-15 22:56:04 +00:00
|
|
|
{TEST_GROUP("rust_ffi"), test_rust_ffi},
|
2021-07-31 00:51:42 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
void list_tests() {
|
|
|
|
std::set<std::string> groups;
|
|
|
|
for (const auto &test : s_tests) {
|
|
|
|
groups.insert(test.group);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto &group : groups) {
|
|
|
|
std::fprintf(stdout, "%s\n", group.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Main test.
|
|
|
|
int main(int argc, char **argv) {
|
2022-08-21 06:14:48 +00:00
|
|
|
std::setlocale(LC_ALL, "");
|
2021-04-29 05:58:28 +00:00
|
|
|
|
Make `test` a custom target again and add top-level test targets
Even though we are using CMake's ctest for testing, we still define our
own `make test` target rather than use its default for many reasons:
* CMake doesn't run tests in-proc or even add each tests as an
individual node in the ninja dependency tree, instead it just bundles
all tests into a target called `test` that always just shells out to
`ctest`, so there are no build-related benefits to not doing that
ourselves.
* CMake devs insist that it is appropriate for `make test` to never
depend on `make all`, i.e. running `make test` does not require any
of the binaries to be built before testing.
* The only way to have a test depend on a binary is to add a fake test
with a name like "build_fish" that executes CMake recursively to
build the `fish` target.
* It is not possible to set top-level CTest options/settings such as
CTEST_PARALLEL_LEVEL from within the CMake configuration file.
* Circling back to the point about individual tests not being actual
Makefile targets, CMake does not offer any way to execute a named
test via the `make`/`ninja`/whatever interface; the only way to
manually invoke test `foo` is to to manually run `ctest` and specify
a regex matching `foo` as an argument, e.g. `ctest -R ^foo$`... which
is really crazy.
With this patch, it is now possible to execute any single test by name,
by invoking the build directly, e.g. to run the `universal.fish` check:
`cmake --build build --target universal.fish` or
`ninja -C build universal.fish`. Unfortunately, this is not integrated
into the Makefile wrapper, so `make universal.fish` won't work (although
this can potentially be hacked around).
2021-08-08 23:31:50 +00:00
|
|
|
if (argc >= 2 && std::strcmp(argv[1], "--list") == 0) {
|
|
|
|
list_tests();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-08-04 19:29:05 +00:00
|
|
|
// Look for the file tests/test.fish. We expect to run in a directory containing that file.
|
2016-05-01 01:37:19 +00:00
|
|
|
// If we don't find it, walk up the directory hierarchy until we do, or error.
|
|
|
|
while (access("./tests/test.fish", F_OK) != 0) {
|
2014-08-04 19:29:05 +00:00
|
|
|
char wd[PATH_MAX + 1] = {};
|
2016-05-01 01:37:19 +00:00
|
|
|
if (!getcwd(wd, sizeof wd)) {
|
2015-07-26 09:15:59 +00:00
|
|
|
perror("getcwd");
|
|
|
|
exit(-1);
|
|
|
|
}
|
2019-03-12 22:07:07 +00:00
|
|
|
if (!std::strcmp(wd, "/")) {
|
2019-05-05 10:09:25 +00:00
|
|
|
std::fwprintf(
|
|
|
|
stderr, L"Unable to find 'tests' directory, which should contain file test.fish\n");
|
2014-08-04 19:29:05 +00:00
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
if (chdir(dirname(wd)) < 0) {
|
2014-08-04 19:29:05 +00:00
|
|
|
perror("chdir");
|
|
|
|
}
|
|
|
|
}
|
2015-09-18 04:56:07 +00:00
|
|
|
|
2021-08-17 22:57:52 +00:00
|
|
|
srandom((unsigned int)time(nullptr));
|
2012-05-14 03:19:02 +00:00
|
|
|
configure_thread_assertions_for_testing();
|
2013-05-05 09:33:17 +00:00
|
|
|
|
2016-12-03 21:27:50 +00:00
|
|
|
// Set the program name to this sentinel value
|
|
|
|
// This will prevent some misleading stderr output during the tests
|
|
|
|
program_name = TESTS_PROGRAM_NAME;
|
2013-10-13 23:58:40 +00:00
|
|
|
s_arguments = argv + 1;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-23 02:00:13 +00:00
|
|
|
struct utsname uname_info;
|
|
|
|
uname(&uname_info);
|
|
|
|
|
2020-01-02 01:24:14 +00:00
|
|
|
say(L"Testing low-level functionality");
|
2023-01-14 22:56:24 +00:00
|
|
|
rust_init();
|
2014-01-02 20:37:50 +00:00
|
|
|
proc_init();
|
2012-11-19 00:30:30 +00:00
|
|
|
env_init();
|
2017-02-16 04:09:26 +00:00
|
|
|
misc_init();
|
2016-06-24 00:24:19 +00:00
|
|
|
reader_init();
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Set default signal handlers, so we can ctrl-C out of this.
|
2014-01-02 20:37:50 +00:00
|
|
|
signal_reset_handlers();
|
2013-07-23 01:26:15 +00:00
|
|
|
|
2019-02-05 22:51:27 +00:00
|
|
|
// Set PWD from getcwd - fixes #5599
|
|
|
|
env_stack_t::principal().set_pwd_from_getcwd();
|
|
|
|
|
Make `test` a custom target again and add top-level test targets
Even though we are using CMake's ctest for testing, we still define our
own `make test` target rather than use its default for many reasons:
* CMake doesn't run tests in-proc or even add each tests as an
individual node in the ninja dependency tree, instead it just bundles
all tests into a target called `test` that always just shells out to
`ctest`, so there are no build-related benefits to not doing that
ourselves.
* CMake devs insist that it is appropriate for `make test` to never
depend on `make all`, i.e. running `make test` does not require any
of the binaries to be built before testing.
* The only way to have a test depend on a binary is to add a fake test
with a name like "build_fish" that executes CMake recursively to
build the `fish` target.
* It is not possible to set top-level CTest options/settings such as
CTEST_PARALLEL_LEVEL from within the CMake configuration file.
* Circling back to the point about individual tests not being actual
Makefile targets, CMake does not offer any way to execute a named
test via the `make`/`ninja`/whatever interface; the only way to
manually invoke test `foo` is to to manually run `ctest` and specify
a regex matching `foo` as an argument, e.g. `ctest -R ^foo$`... which
is really crazy.
With this patch, it is now possible to execute any single test by name,
by invoking the build directly, e.g. to run the `universal.fish` check:
`cmake --build build --target universal.fish` or
`ninja -C build universal.fish`. Unfortunately, this is not integrated
into the Makefile wrapper, so `make universal.fish` won't work (although
this can potentially be hacked around).
2021-08-08 23:31:50 +00:00
|
|
|
for (const auto &test : s_tests) {
|
|
|
|
if (should_test_function(test.group)) {
|
|
|
|
test.test();
|
|
|
|
}
|
|
|
|
}
|
2020-06-07 23:05:52 +00:00
|
|
|
|
2012-11-19 00:30:30 +00:00
|
|
|
say(L"Encountered %d errors in low-level tests", err_count);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (s_test_run_count == 0) say(L"*** No Tests Were Actually Run! ***");
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2023-04-29 17:07:59 +00:00
|
|
|
// If under ASAN, reclaim some resources before exiting.
|
|
|
|
asan_before_exit();
|
|
|
|
|
2020-01-02 01:24:14 +00:00
|
|
|
if (err_count != 0) {
|
|
|
|
return 1;
|
2014-01-13 14:35:38 +00:00
|
|
|
}
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|