Merge #8519 feat: name, test ids, sockets in stdpath(state)

This commit is contained in:
Justin M. Keyes
2022-06-17 01:23:48 +02:00
committed by GitHub
21 changed files with 370 additions and 257 deletions

View File

@ -6628,30 +6628,29 @@ serverlist() *serverlist()*
serverstart([{address}]) *serverstart()* serverstart([{address}]) *serverstart()*
Opens a socket or named pipe at {address} and listens for Opens a socket or named pipe at {address} and listens for
|RPC| messages. Clients can send |API| commands to the address |RPC| messages. Clients can send |API| commands to the
to control Nvim. returned address to control Nvim.
Returns the address string. Returns the address string (may differ from the requested
{address}).
- If {address} contains a colon ":" it is interpreted as
a TCP/IPv4/IPv6 address where the last ":" separates host
and port (empty or zero assigns a random port).
- Else it is interpreted as a named pipe or Unix domain socket
path. If there are no slashes it is treated as a name and
appended to a generated path.
- If {address} is empty it generates a path.
If {address} does not contain a colon ":" it is interpreted as Example named pipe: >
a named pipe or Unix domain socket path.
Example: >
if has('win32') if has('win32')
call serverstart('\\.\pipe\nvim-pipe-1234') echo serverstart('\\.\pipe\nvim-pipe-1234')
else else
call serverstart('nvim.sock') echo serverstart('nvim.sock')
endif endif
< <
If {address} contains a colon ":" it is interpreted as a TCP Example TCP/IP address: >
address where the last ":" separates the host and port. echo serverstart('::1:12345')
Assigns a random port if it is empty or 0. Supports IPv4/IPv6.
Example: >
:call serverstart('::1:12345')
<
If no address is given, it is equivalent to: >
:call serverstart(tempname())
serverstop({address}) *serverstop()* serverstop({address}) *serverstop()*
Closes the pipe or socket at {address}. Closes the pipe or socket at {address}.
@ -7545,7 +7544,7 @@ stdpath({what}) *stdpath()* *E6100*
data_dirs List Other data directories. data_dirs List Other data directories.
log String Logs directory (for use by plugins too). log String Logs directory (for use by plugins too).
state String Session state directory: storage for file state String Session state directory: storage for file
drafts, undo history, shada, etc. drafts, undo, shada, named pipes, ...
Example: > Example: >
:echo stdpath("config") :echo stdpath("config")

View File

@ -1790,8 +1790,9 @@ Dictionary nvim__stats(void)
{ {
Dictionary rv = ARRAY_DICT_INIT; Dictionary rv = ARRAY_DICT_INIT;
PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync)); PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); PUT(rv, "log_skip", INTEGER_OBJ(g_stats.log_skip));
PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count())); PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count()));
PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
return rv; return rv;
} }

View File

@ -8497,7 +8497,7 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
address = xstrdup(tv_get_string(argvars)); address = xstrdup(tv_get_string(argvars));
} }
} else { } else {
address = server_address_new(); address = server_address_new(NULL);
} }
int result = server_start(address); int result = server_start(address);

View File

@ -120,7 +120,7 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
proc->internal_close_cb = decref; proc->internal_close_cb = decref;
proc->refcount++; proc->refcount++;
kl_push(WatcherPtr, proc->loop->children, proc); kl_push(WatcherPtr, proc->loop->children, proc);
DLOG("new: pid=%d argv=[%s]", proc->pid, *proc->argv); DLOG("new: pid=%d argv=[%s]", proc->pid, proc->argv[0]);
return 0; return 0;
} }

View File

@ -16,15 +16,15 @@
#include <uv.h> #include <uv.h>
#include "auto/config.h" #include "auto/config.h"
#include "nvim/eval.h"
#include "nvim/log.h" #include "nvim/log.h"
#include "nvim/main.h" #include "nvim/main.h"
#include "nvim/message.h" #include "nvim/message.h"
#include "nvim/os/os.h" #include "nvim/os/os.h"
#include "nvim/os/time.h" #include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/types.h" #include "nvim/types.h"
#define LOG_FILE_ENV "NVIM_LOG_FILE"
/// Cached location of the expanded log file path decided by log_path_init(). /// Cached location of the expanded log file path decided by log_path_init().
static char log_file_path[MAXPATHL + 1] = { 0 }; static char log_file_path[MAXPATHL + 1] = { 0 };
@ -52,7 +52,7 @@ static bool log_try_create(char *fname)
return true; return true;
} }
/// Initializes path to log file. Sets $NVIM_LOG_FILE if empty. /// Initializes the log file path and sets $NVIM_LOG_FILE if empty.
/// ///
/// Tries $NVIM_LOG_FILE, or falls back to $XDG_STATE_HOME/nvim/log. Failed /// Tries $NVIM_LOG_FILE, or falls back to $XDG_STATE_HOME/nvim/log. Failed
/// initialization indicates either a bug in expand_env() or both $NVIM_LOG_FILE /// initialization indicates either a bug in expand_env() or both $NVIM_LOG_FILE
@ -60,9 +60,8 @@ static bool log_try_create(char *fname)
static void log_path_init(void) static void log_path_init(void)
{ {
size_t size = sizeof(log_file_path); size_t size = sizeof(log_file_path);
expand_env((char_u *)"$" LOG_FILE_ENV, (char_u *)log_file_path, expand_env((char_u *)"$" ENV_LOGFILE, (char_u *)log_file_path, (int)size - 1);
(int)size - 1); if (strequal("$" ENV_LOGFILE, log_file_path)
if (strequal("$" LOG_FILE_ENV, log_file_path)
|| log_file_path[0] == '\0' || log_file_path[0] == '\0'
|| os_isdir((char_u *)log_file_path) || os_isdir((char_u *)log_file_path)
|| !log_try_create(log_file_path)) { || !log_try_create(log_file_path)) {
@ -87,7 +86,7 @@ static void log_path_init(void)
log_file_path[0] = '\0'; log_file_path[0] = '\0';
return; return;
} }
os_setenv(LOG_FILE_ENV, log_file_path, true); os_setenv(ENV_LOGFILE, log_file_path, true);
if (log_dir_failure) { if (log_dir_failure) {
WLOG("Failed to create directory %s for writing logs: %s", WLOG("Failed to create directory %s for writing logs: %s",
failed_dir, os_strerror(log_dir_failure)); failed_dir, os_strerror(log_dir_failure));
@ -209,7 +208,7 @@ FILE *open_log_file(void)
// - Directory does not exist // - Directory does not exist
// - File is not writable // - File is not writable
do_log_to_file(stderr, LOGLVL_ERR, NULL, __func__, __LINE__, true, do_log_to_file(stderr, LOGLVL_ERR, NULL, __func__, __LINE__, true,
"failed to open $" LOG_FILE_ENV " (%s): %s", "failed to open $" ENV_LOGFILE " (%s): %s",
strerror(errno), log_file_path); strerror(errno), log_file_path);
return stderr; return stderr;
} }
@ -277,6 +276,9 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context,
va_list args) va_list args)
FUNC_ATTR_PRINTF(7, 0) FUNC_ATTR_PRINTF(7, 0)
{ {
// Name of the Nvim instance that produced the log.
static char name[16] = { 0 };
static const char *log_levels[] = { static const char *log_levels[] = {
[LOGLVL_DBG] = "DBG", [LOGLVL_DBG] = "DBG",
[LOGLVL_INF] = "INF", [LOGLVL_INF] = "INF",
@ -291,8 +293,7 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context,
return false; return false;
} }
char date_time[20]; char date_time[20];
if (strftime(date_time, sizeof(date_time), "%Y-%m-%dT%H:%M:%S", if (strftime(date_time, sizeof(date_time), "%Y-%m-%dT%H:%M:%S", &local_time) == 0) {
&local_time) == 0) {
return false; return false;
} }
@ -302,14 +303,30 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context,
millis = (int)curtime.tv_usec / 1000; millis = (int)curtime.tv_usec / 1000;
} }
// Get a name for this Nvim instance.
// TODO(justinmk): expose this as v:name ?
if (starting || name[0] == '\0') {
// Parent servername.
const char *parent = path_tail(os_getenv(ENV_NVIM));
// Servername. Empty until starting=false.
const char *serv = path_tail(get_vim_var_str(VV_SEND_SERVER));
if (parent && parent[0] != NUL) {
snprintf(name, sizeof(name), "%s/c", parent); // "/c" indicates child.
} else if (serv && serv[0] != NUL) {
snprintf(name, sizeof(name), "%s", serv ? serv : "");
} else {
int64_t pid = os_get_pid();
snprintf(name, sizeof(name), "?.%-5" PRId64, pid);
}
}
// Print the log message. // Print the log message.
int64_t pid = os_get_pid();
int rv = (line_num == -1 || func_name == NULL) int rv = (line_num == -1 || func_name == NULL)
? fprintf(log_file, "%s %s.%03d %-5" PRId64 " %s", ? fprintf(log_file, "%s %s.%03d %-10s %s",
log_levels[log_level], date_time, millis, pid, log_levels[log_level], date_time, millis, name,
(context == NULL ? "?:" : context)) (context == NULL ? "?:" : context))
: fprintf(log_file, "%s %s.%03d %-5" PRId64 " %s%s:%d: ", : fprintf(log_file, "%s %s.%03d %-10s %s%s:%d: ",
log_levels[log_level], date_time, millis, pid, log_levels[log_level], date_time, millis, name,
(context == NULL ? "" : context), (context == NULL ? "" : context),
func_name, line_num); func_name, line_num);
if (rv < 0) { if (rv < 0) {

View File

@ -23,7 +23,6 @@
#define MAX_CONNECTIONS 32 #define MAX_CONNECTIONS 32
#define ENV_LISTEN "NVIM_LISTEN_ADDRESS" // deprecated #define ENV_LISTEN "NVIM_LISTEN_ADDRESS" // deprecated
#define ENV_NVIM "NVIM"
static garray_T watchers = GA_EMPTY_INIT_VALUE; static garray_T watchers = GA_EMPTY_INIT_VALUE;
@ -43,7 +42,7 @@ bool server_init(const char *listen_addr)
int rv = listen_addr ? server_start(listen_addr) : 1; int rv = listen_addr ? server_start(listen_addr) : 1;
if (0 != rv) { if (0 != rv) {
listen_addr = server_address_new(); listen_addr = server_address_new(NULL);
if (!listen_addr) { if (!listen_addr) {
return false; return false;
} }
@ -56,6 +55,11 @@ bool server_init(const char *listen_addr)
os_unsetenv(ENV_LISTEN); os_unsetenv(ENV_LISTEN);
} }
// TODO(justinmk): this is for logging_spec. Can remove this after nvim_log #7062 is merged.
if (os_env_exists("__NVIM_TEST_LOG")) {
ELOG("test log message");
}
return rv == 0; return rv == 0;
} }
@ -83,23 +87,26 @@ void server_teardown(void)
/// Generates unique address for local server. /// Generates unique address for local server.
/// ///
/// In Windows this is a named pipe in the format /// Named pipe format:
/// \\.\pipe\nvim-<PID>-<COUNTER>. /// - Windows: "\\.\pipe\<name>.<pid>.<counter>"
/// /// - Other: "~/.local/state/nvim/<name>.<pid>.<counter>"
/// For other systems it is a path returned by vim_tempname(). char *server_address_new(const char *name)
///
/// This function is NOT thread safe
char *server_address_new(void)
{ {
#ifdef WIN32
static uint32_t count = 0; static uint32_t count = 0;
char template[ADDRESS_MAX_SIZE]; char fmt[ADDRESS_MAX_SIZE];
snprintf(template, ADDRESS_MAX_SIZE, #ifdef WIN32
"\\\\.\\pipe\\nvim-%" PRIu64 "-%" PRIu32, os_get_pid(), count++); int r = snprintf(fmt, sizeof(fmt), "\\\\.\\pipe\\%s.%" PRIu64 ".%" PRIu32,
return xstrdup(template); name ? name : "nvim", os_get_pid(), count++);
#else #else
return (char *)vim_tempname(); char *dir = get_xdg_home(kXDGStateHome);
int r = snprintf(fmt, sizeof(fmt), "%s/%s.%" PRIu64 ".%" PRIu32,
dir, name ? name : "nvim", os_get_pid(), count++);
xfree(dir);
#endif #endif
if ((size_t)r >= sizeof(fmt)) {
ELOG("truncated server address");
}
return xstrdup(fmt);
} }
/// Check if this instance owns a pipe address. /// Check if this instance owns a pipe address.
@ -114,35 +121,35 @@ bool server_owns_pipe_address(const char *path)
return false; return false;
} }
/// Starts listening for API calls. /// Starts listening for RPC calls.
/// ///
/// The socket type is determined by parsing `endpoint`: If it's a valid IPv4 /// Socket type is decided by the format of `addr`:
/// or IPv6 address in 'ip:[port]' format, then it will be a TCP socket. /// - TCP socket if it looks like an IPv4/6 address ("ip:[port]").
/// Otherwise it will be a Unix socket or named pipe (Windows). /// - If [port] is omitted, a random one is assigned.
/// - Unix socket (or named pipe on Windows) otherwise.
/// - If the name doesn't contain slashes it is appended to a generated path. #8519
/// ///
/// If no port is given, a random one will be assigned. /// @param addr Server address: a "ip:[port]" string or arbitrary name or filepath (max 256 bytes)
/// /// for the Unix socket or named pipe.
/// @param endpoint Address of the server. Either a 'ip:[port]' string or an /// @returns 0: success, 1: validation error, 2: already listening, -errno: failed to bind/listen.
/// arbitrary identifier (trimmed to 256 bytes) for the Unix int server_start(const char *addr)
/// socket or named pipe.
/// @returns 0: success, 1: validation error, 2: already listening,
/// -errno: failed to bind or listen.
int server_start(const char *endpoint)
{ {
if (endpoint == NULL || endpoint[0] == '\0') { if (addr == NULL || addr[0] == '\0') {
WLOG("Empty or NULL endpoint"); WLOG("Empty or NULL address");
return 1; return 1;
} }
bool isname = !strstr(addr, ":") && !strstr(addr, "/") && !strstr(addr, "\\");
char *addr_gen = isname ? server_address_new(addr) : NULL;
SocketWatcher *watcher = xmalloc(sizeof(SocketWatcher)); SocketWatcher *watcher = xmalloc(sizeof(SocketWatcher));
int result = socket_watcher_init(&main_loop, watcher, isname ? addr_gen : addr);
int result = socket_watcher_init(&main_loop, watcher, endpoint); xfree(addr_gen);
if (result < 0) { if (result < 0) {
xfree(watcher); xfree(watcher);
return result; return result;
} }
// Check if a watcher for the endpoint already exists // Check if a watcher for the address already exists.
for (int i = 0; i < watchers.ga_len; i++) { for (int i = 0; i < watchers.ga_len; i++) {
if (!strcmp(watcher->addr, ((SocketWatcher **)watchers.ga_data)[i]->addr)) { if (!strcmp(watcher->addr, ((SocketWatcher **)watchers.ga_data)[i]->addr)) {
ELOG("Already listening on %s", watcher->addr); ELOG("Already listening on %s", watcher->addr);

View File

@ -16,4 +16,7 @@
# include "os/users.h.generated.h" # include "os/users.h.generated.h"
#endif #endif
#define ENV_LOGFILE "NVIM_LOG_FILE"
#define ENV_NVIM "NVIM"
#endif // NVIM_OS_OS_H #endif // NVIM_OS_OS_H

View File

@ -163,10 +163,15 @@ static struct termios termios_default;
/// @param tty_fd TTY file descriptor, or -1 if not in a terminal. /// @param tty_fd TTY file descriptor, or -1 if not in a terminal.
void pty_process_save_termios(int tty_fd) void pty_process_save_termios(int tty_fd)
{ {
DLOG("tty_fd=%d", tty_fd); if (tty_fd == -1) {
if (tty_fd == -1 || tcgetattr(tty_fd, &termios_default) != 0) {
return; return;
} }
int rv = tcgetattr(tty_fd, &termios_default);
if (rv != 0) {
ELOG("tcgetattr failed (tty_fd=%d): %s", tty_fd, strerror(errno));
} else {
DLOG("tty_fd=%d", tty_fd);
}
} }
/// @returns zero on success, or negative error code /// @returns zero on success, or negative error code

View File

@ -88,7 +88,12 @@ FileComparison path_full_compare(char_u *const s1, char_u *const s2, const bool
return kDifferentFiles; return kDifferentFiles;
} }
/// Gets the tail (i.e., the filename segment) of a path `fname`. /// Gets the tail (filename segment) of path `fname`.
///
/// Examples:
/// - "dir/file.txt" => "file.txt"
/// - "file.txt" => "file.txt"
/// - "dir/" => ""
/// ///
/// @return pointer just past the last path separator (empty string, if fname /// @return pointer just past the last path separator (empty string, if fname
/// ends in a slash), or empty string if fname is NULL. /// ends in a slash), or empty string if fname is NULL.

View File

@ -91,25 +91,33 @@ or:
Debugging tests Debugging tests
--------------- ---------------
- Each test gets a test id which looks like "T123". This also appears in the
log file. Child processes spawned from a test appear in the logs with the
*parent* name followed by "/c". Example:
```
DBG 2022-06-15T18:37:45.226 T57.58016.0 UI: flush
DBG 2022-06-15T18:37:45.226 T57.58016.0 inbuf_poll:442: blocking... events_enabled=0 events_pending=0
DBG 2022-06-15T18:37:45.227 T57.58016.0/c UI: stop
INF 2022-06-15T18:37:45.227 T57.58016.0/c os_exit:595: Nvim exit: 0
DBG 2022-06-15T18:37:45.229 T57.58016.0 read_cb:118: closing Stream (0x7fd5d700ea18): EOF (end of file)
INF 2022-06-15T18:37:45.229 T57.58016.0 on_process_exit:400: exited: pid=58017 status=0 stoptime=0
```
- You can set `$GDB` to [run tests under gdbserver](https://github.com/neovim/neovim/pull/1527). - You can set `$GDB` to [run tests under gdbserver](https://github.com/neovim/neovim/pull/1527).
And if `$VALGRIND` is set it will pass `--vgdb=yes` to valgrind instead of And if `$VALGRIND` is set it will pass `--vgdb=yes` to valgrind instead of
starting gdbserver directly. starting gdbserver directly.
- Hanging tests often happen due to unexpected `:h press-enter` prompts. The - Hanging tests can happen due to unexpected "press-enter" prompts. The
default screen width is 50 columns. Commands that try to print lines longer default screen width is 50 columns. Commands that try to print lines longer
than 50 columns in the command-line, e.g. `:edit very...long...path`, will than 50 columns in the command-line, e.g. `:edit very...long...path`, will
trigger the prompt. In this case, a shorter path or `:silent edit` should be trigger the prompt. Try using a shorter path, or `:silent edit`.
used.
- If you can't figure out what is going on, try to visualize the screen. Put - If you can't figure out what is going on, try to visualize the screen. Put
this at the beginning of your test: this at the beginning of your test:
```lua
```lua local Screen = require('test.functional.ui.screen')
local Screen = require('test.functional.ui.screen') local screen = Screen.new()
local screen = Screen.new() screen:attach()
screen:attach() ```
``` Then put `screen:snapshot_util()` anywhere in your test. See the comments in
`test/functional/ui/screen.lua` for more info.
Afterwards, put `screen:snapshot_util()` at any position in your test. See the
comment at the top of `test/functional/ui/screen.lua` for more.
Filtering Tests Filtering Tests
--------------- ---------------
@ -247,12 +255,17 @@ Number; !must be defined to function properly):
- `BUSTED_ARGS` (F) (U): arguments forwarded to `busted`. - `BUSTED_ARGS` (F) (U): arguments forwarded to `busted`.
- `CC` (U) (S): specifies which C compiler to use to preprocess files.
Currently only compilers with gcc-compatible arguments are supported.
- `GDB` (F) (D): makes nvim instances to be run under `gdbserver`. It will be - `GDB` (F) (D): makes nvim instances to be run under `gdbserver`. It will be
accessible on `localhost:7777`: use `gdb build/bin/nvim`, type `target remote accessible on `localhost:7777`: use `gdb build/bin/nvim`, type `target remote
:7777` inside. :7777` inside.
- `GDBSERVER_PORT` (F) (I): overrides port used for `GDB`. - `GDBSERVER_PORT` (F) (I): overrides port used for `GDB`.
- `LOG_DIR` (FU) (S!): specifies where to seek for valgrind and ASAN log files.
- `VALGRIND` (F) (D): makes nvim instances to be run under `valgrind`. Log - `VALGRIND` (F) (D): makes nvim instances to be run under `valgrind`. Log
files are named `valgrind-%p.log` in this case. Note that non-empty valgrind files are named `valgrind-%p.log` in this case. Note that non-empty valgrind
log may fail tests. Valgrind arguments may be seen in log may fail tests. Valgrind arguments may be seen in
@ -269,11 +282,7 @@ Number; !must be defined to function properly):
- `NVIM_LUA_NOTRACK` (F) (D): disable reference counting of Lua objects - `NVIM_LUA_NOTRACK` (F) (D): disable reference counting of Lua objects
- `NVIM_PROG`, `NVIM_PRG` (F) (S): override path to Neovim executable (default - `NVIM_PRG` (F) (S): path to Nvim executable (default: `build/bin/nvim`).
to `build/bin/nvim`).
- `CC` (U) (S): specifies which C compiler to use to preprocess files.
Currently only compilers with gcc-compatible arguments are supported.
- `NVIM_TEST_MAIN_CDEFS` (U) (1): makes `ffi.cdef` run in main process. This - `NVIM_TEST_MAIN_CDEFS` (U) (1): makes `ffi.cdef` run in main process. This
raises a possibility of bugs due to conflicts in header definitions, despite raises a possibility of bugs due to conflicts in header definitions, despite
@ -295,8 +304,6 @@ Number; !must be defined to function properly):
- `NVIM_TEST_RUN_FAILING_TESTS` (U) (1): makes `itp` run tests which are known - `NVIM_TEST_RUN_FAILING_TESTS` (U) (1): makes `itp` run tests which are known
to fail (marked by setting third argument to `true`). to fail (marked by setting third argument to `true`).
- `LOG_DIR` (FU) (S!): specifies where to seek for valgrind and ASAN log files.
- `NVIM_TEST_CORE_*` (FU) (S): a set of environment variables which specify - `NVIM_TEST_CORE_*` (FU) (S): a set of environment variables which specify
where to search for core files. Are supposed to be defined all at once. where to search for core files. Are supposed to be defined all at once.

View File

@ -1,4 +1,5 @@
local pretty = require 'pl.pretty' local pretty = require 'pl.pretty'
local global_helpers = require('test.helpers')
-- Colors are disabled by default. #15610 -- Colors are disabled by default. #15610
local colors = setmetatable({}, {__index = function() return function(s) return s == nil and '' or tostring(s) end end}) local colors = setmetatable({}, {__index = function() return function(s) return s == nil and '' or tostring(s) end end})
@ -25,35 +26,35 @@ return function(options)
local repeatSuiteString = '\nRepeating all tests (run %d of %d) . . .\n\n' local repeatSuiteString = '\nRepeating all tests (run %d of %d) . . .\n\n'
local randomizeString = c.note('Note: Randomizing test order with a seed of %d.\n') local randomizeString = c.note('Note: Randomizing test order with a seed of %d.\n')
local globalSetup = c.sect('[----------]') .. ' Global test environment setup.\n' local globalSetup = c.sect('--------') .. ' Global test environment setup.\n'
local fileStartString = c.sect('[----------]') .. ' Running tests from ' .. c.file('%s') .. '\n' local fileStartString = c.sect('--------') .. ' Running tests from ' .. c.file('%s') .. '\n'
local runString = c.sect('[ RUN ]') .. ' ' .. c.test('%s') .. ': ' local runString = c.sect('RUN ') .. ' ' .. c.test('%s') .. ': '
local successString = c.succ('OK') .. '\n' local successString = c.succ('OK') .. '\n'
local skippedString = c.skip('SKIP') .. '\n' local skippedString = c.skip('SKIP') .. '\n'
local failureString = c.fail('FAIL') .. '\n' local failureString = c.fail('FAIL') .. '\n'
local errorString = c.errr('ERR') .. '\n' local errorString = c.errr('ERR') .. '\n'
local fileEndString = c.sect('[----------]') .. ' '.. c.nmbr('%d') .. ' %s from ' .. c.file('%s') .. ' ' .. c.time('(%.2f ms total)') .. '\n\n' local fileEndString = c.sect('--------') .. ' '.. c.nmbr('%d') .. ' %s from ' .. c.file('%s') .. ' ' .. c.time('(%.2f ms total)') .. '\n\n'
local globalTeardown = c.sect('[----------]') .. ' Global test environment teardown.\n' local globalTeardown = c.sect('--------') .. ' Global test environment teardown.\n'
local suiteEndString = c.sect('[==========]') .. ' ' .. c.nmbr('%d') .. ' %s from ' .. c.nmbr('%d') .. ' test %s ran. ' .. c.time('(%.2f ms total)') .. '\n' local suiteEndString = c.sect('========') .. ' ' .. c.nmbr('%d') .. ' %s from ' .. c.nmbr('%d') .. ' test %s ran. ' .. c.time('(%.2f ms total)') .. '\n'
local successStatus = c.succ('[ PASSED ]') .. ' ' .. c.nmbr('%d') .. ' %s.\n' local successStatus = c.succ('PASSED ') .. ' ' .. c.nmbr('%d') .. ' %s.\n'
local timeString = c.time('%.2f ms') local timeString = c.time('%.2f ms')
local summaryStrings = { local summaryStrings = {
skipped = { skipped = {
header = c.skip('[ SKIPPED ]') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', header = c.skip('SKIPPED ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n',
test = c.skip('[ SKIPPED ]') .. ' %s\n', test = c.skip('SKIPPED ') .. ' %s\n',
footer = ' ' .. c.nmbr('%d') .. ' SKIPPED %s\n', footer = ' ' .. c.nmbr('%d') .. ' SKIPPED %s\n',
}, },
failure = { failure = {
header = c.fail('[ FAILED ]') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', header = c.fail('FAILED ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n',
test = c.fail('[ FAILED ]') .. ' %s\n', test = c.fail('FAILED ') .. ' %s\n',
footer = ' ' .. c.nmbr('%d') .. ' FAILED %s\n', footer = ' ' .. c.nmbr('%d') .. ' FAILED %s\n',
}, },
error = { error = {
header = c.errr('[ ERROR ]') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', header = c.errr('ERROR ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n',
test = c.errr('[ ERROR ]') .. ' %s\n', test = c.errr('ERROR ') .. ' %s\n',
footer = ' ' .. c.nmbr('%d') .. ' %s\n', footer = ' ' .. c.nmbr('%d') .. ' %s\n',
}, },
} }
@ -193,6 +194,9 @@ return function(options)
io.write(globalTeardown) io.write(globalTeardown)
io.write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms)) io.write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms))
io.write(getSummaryString()) io.write(getSummaryString())
if failureCount > 0 or errorCount > 0 then
io.write(global_helpers.read_nvim_log(nil, true))
end
io.flush() io.flush()
return nil, true return nil, true
@ -215,7 +219,9 @@ return function(options)
end end
handler.testStart = function(element, _parent) handler.testStart = function(element, _parent)
io.write(runString:format(handler.getFullName(element))) local testid = _G._nvim_test_id or ''
local desc = ('%s %s'):format(testid, handler.getFullName(element))
io.write(runString:format(desc))
io.flush() io.flush()
return nil, true return nil, true

View File

@ -336,7 +336,7 @@ describe('nvim_get_keymap', function()
return GlobalCount return GlobalCount
]]) ]])
local mapargs = meths.get_keymap('n') local mapargs = meths.get_keymap('n')
assert.Truthy(type(mapargs[1].callback) == 'number', 'callback is not luaref number') assert(type(mapargs[1].callback) == 'number', 'callback is not luaref number')
mapargs[1].callback = nil mapargs[1].callback = nil
eq({ eq({
lhs='asdf', lhs='asdf',
@ -815,7 +815,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
assert.truthy(string.match(funcs.maparg('asdf', 'n'), assert.truthy(string.match(funcs.maparg('asdf', 'n'),
"^<Lua function %d+>")) "^<Lua function %d+>"))
local mapargs = funcs.maparg('asdf', 'n', false, true) local mapargs = funcs.maparg('asdf', 'n', false, true)
assert.Truthy(type(mapargs.callback) == 'number', 'callback is not luaref number') assert(type(mapargs.callback) == 'number', 'callback is not luaref number')
mapargs.callback = nil mapargs.callback = nil
eq(generate_mapargs('n', 'asdf', nil, {sid=sid_lua}), mapargs) eq(generate_mapargs('n', 'asdf', nil, {sid=sid_lua}), mapargs)
end) end)

View File

@ -0,0 +1,57 @@
local helpers = require('test.functional.helpers')(after_each)
local assert_log = helpers.assert_log
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local exec_lua = helpers.exec_lua
local expect_exit = helpers.expect_exit
local request = helpers.request
local retry = helpers.retry
describe('log', function()
local testlog = 'Xtest_logging'
after_each(function()
expect_exit(command, 'qa!')
os.remove(testlog)
end)
it('skipped before log_init', function()
-- This test is for _visibility_: adjust as needed, after checking for regression.
--
-- During startup some components may try to log before logging is setup.
-- That should be uncommon (ideally never)--and if there are MANY such
-- calls, that needs investigation.
clear()
eq(0, request('nvim__stats').log_skip)
clear{env={CDPATH='~doesnotexist'}}
assert(request('nvim__stats').log_skip <= 13)
end)
it('messages are formatted with name or test id', function()
-- Examples:
-- ERR 2022-05-29T12:30:03.800 T2 log_init:110: test log message
-- ERR 2022-05-29T12:30:03.814 T2/child log_init:110: test log message
clear({env={
NVIM_LOG_FILE=testlog,
-- TODO: remove this after nvim_log #7062 is merged.
__NVIM_TEST_LOG='1'
}})
local tid = _G._nvim_test_id
retry(nil, 1000, function()
assert_log(tid..'%.%d+%.%d +server_init:%d+: test log message', testlog, 100)
end)
exec_lua([[
local j1 = vim.fn.jobstart({ vim.v.progpath, '-es', '-V1', '+foochild', '+qa!' }, vim.empty_dict())
vim.fn.jobwait({ j1 }, 10000)
]])
-- Child Nvim spawned by jobstart() appends "/c" to parent name.
retry(nil, 1000, function()
assert_log('%.%d+%.%d/c +server_init:%d+: test log message', testlog, 100)
end)
end)
end)

View File

@ -580,7 +580,7 @@ describe('user config init', function()
it('loads default lua config, but shows an error', function() it('loads default lua config, but shows an error', function()
clear{ args_rm={'-u'}, env=xenv } clear{ args_rm={'-u'}, env=xenv }
feed('<cr>') -- confirm "Conflicting config ..." message feed('<cr><c-c>') -- Dismiss "Conflicting config " message.
eq(1, eval('g:lua_rc')) eq(1, eval('g:lua_rc'))
matches('^E5422: Conflicting configs', meths.exec('messages', true)) matches('^E5422: Conflicting configs', meths.exec('messages', true))
end) end)
@ -632,13 +632,13 @@ describe('runtime:', function()
eq(2, eval('g:lua_plugin')) eq(2, eval('g:lua_plugin'))
-- Check if plugin_file_path is listed in :scriptname -- Check if plugin_file_path is listed in :scriptname
local scripts = meths.exec(':scriptnames', true) local scripts = meths.exec(':scriptnames', true)
assert.Truthy(scripts:find(plugin_file_path)) assert(scripts:find(plugin_file_path))
-- Check if plugin_file_path is listed in startup profile -- Check if plugin_file_path is listed in startup profile
local profile_reader = io.open(profiler_file, 'r') local profile_reader = io.open(profiler_file, 'r')
local profile_log = profile_reader:read('*a') local profile_log = profile_reader:read('*a')
profile_reader:close() profile_reader:close()
assert.Truthy(profile_log :find(plugin_file_path)) assert(profile_log:find(plugin_file_path))
os.remove(profiler_file) os.remove(profiler_file)
rmdir(plugin_path) rmdir(plugin_path)

View File

@ -431,18 +431,25 @@ end
function module.new_argv(...) function module.new_argv(...)
local args = {unpack(module.nvim_argv)} local args = {unpack(module.nvim_argv)}
table.insert(args, '--headless') table.insert(args, '--headless')
if _G._nvim_test_id then
-- Set the server name to the test-id for logging. #8519
table.insert(args, '--listen')
table.insert(args, _G._nvim_test_id)
end
local new_args local new_args
local io_extra local io_extra
local env = nil local env = nil
local opts = select(1, ...) local opts = select(1, ...)
if type(opts) == 'table' then if type(opts) ~= 'table' then
new_args = {...}
else
args = remove_args(args, opts.args_rm) args = remove_args(args, opts.args_rm)
if opts.env then if opts.env then
local env_tbl = {} local env_opt = {}
for k, v in pairs(opts.env) do for k, v in pairs(opts.env) do
assert(type(k) == 'string') assert(type(k) == 'string')
assert(type(v) == 'string') assert(type(v) == 'string')
env_tbl[k] = v env_opt[k] = v
end end
for _, k in ipairs({ for _, k in ipairs({
'HOME', 'HOME',
@ -458,19 +465,18 @@ function module.new_argv(...)
'TMPDIR', 'TMPDIR',
'VIMRUNTIME', 'VIMRUNTIME',
}) do }) do
if not env_tbl[k] then -- Set these from the environment unless the caller defined them.
env_tbl[k] = os.getenv(k) if not env_opt[k] then
env_opt[k] = os.getenv(k)
end end
end end
env = {} env = {}
for k, v in pairs(env_tbl) do for k, v in pairs(env_opt) do
env[#env + 1] = k .. '=' .. v env[#env + 1] = k .. '=' .. v
end end
end end
new_args = opts.args or {} new_args = opts.args or {}
io_extra = opts.io_extra io_extra = opts.io_extra
else
new_args = {...}
end end
for _, arg in ipairs(new_args) do for _, arg in ipairs(new_args) do
table.insert(args, arg) table.insert(args, arg)

View File

@ -266,8 +266,8 @@ describe('LSP', function()
end; end;
-- If the program timed out, then code will be nil. -- If the program timed out, then code will be nil.
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
-- Note that NIL must be used here. -- Note that NIL must be used here.
-- on_handler(err, method, result, client_id) -- on_handler(err, method, result, client_id)
@ -288,8 +288,8 @@ describe('LSP', function()
client.stop() client.stop()
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(101, code, "exit code", fake_lsp_logfile) -- See fake-lsp-server.lua eq(101, code, "exit code") -- See fake-lsp-server.lua
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
assert_log(pesc([[assert_eq failed: left == "\"shutdown\"", right == "\"test\""]]), assert_log(pesc([[assert_eq failed: left == "\"shutdown\"", right == "\"test\""]]),
fake_lsp_logfile) fake_lsp_logfile)
end; end;
@ -335,8 +335,8 @@ describe('LSP', function()
client.stop() client.stop()
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(...) on_handler = function(...)
eq(table.remove(expected_handlers), {...}, "expected handler") eq(table.remove(expected_handlers), {...}, "expected handler")
@ -367,8 +367,8 @@ describe('LSP', function()
client.notify('finish') client.notify('finish')
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
@ -436,8 +436,8 @@ describe('LSP', function()
client = _client client = _client
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
@ -496,8 +496,8 @@ describe('LSP', function()
eq(false, client.server_capabilities().codeLensProvider) eq(false, client.server_capabilities().codeLensProvider)
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(...) on_handler = function(...)
eq(table.remove(expected_handlers), {...}, "expected handler") eq(table.remove(expected_handlers), {...}, "expected handler")
@ -517,8 +517,8 @@ describe('LSP', function()
client = c client = c
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
@ -547,8 +547,8 @@ describe('LSP', function()
client = c client = c
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
@ -596,8 +596,8 @@ describe('LSP', function()
eq(true, client.supports_method("unknown-method")) eq(true, client.supports_method("unknown-method"))
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(...) on_handler = function(...)
eq(table.remove(expected_handlers), {...}, "expected handler") eq(table.remove(expected_handlers), {...}, "expected handler")
@ -626,8 +626,8 @@ describe('LSP', function()
]] ]]
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(...) on_handler = function(...)
eq(table.remove(expected_handlers), {...}, "expected handler") eq(table.remove(expected_handlers), {...}, "expected handler")
@ -651,8 +651,8 @@ describe('LSP', function()
exec_lua("vim.lsp.buf.type_definition()") exec_lua("vim.lsp.buf.type_definition()")
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(...) on_handler = function(...)
eq(table.remove(expected_handlers), {...}, "expected handler") eq(table.remove(expected_handlers), {...}, "expected handler")
@ -672,8 +672,8 @@ describe('LSP', function()
client = _client client = _client
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
eq(0, #expected_handlers, "did not call expected handler") eq(0, #expected_handlers, "did not call expected handler")
end; end;
on_handler = function(err, _, ctx) on_handler = function(err, _, ctx)
@ -696,8 +696,8 @@ describe('LSP', function()
client = _client client = _client
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
eq(0, #expected_handlers, "did not call expected handler") eq(0, #expected_handlers, "did not call expected handler")
end; end;
on_handler = function(err, _, ctx) on_handler = function(err, _, ctx)
@ -726,8 +726,8 @@ describe('LSP', function()
client.notify("release") client.notify("release")
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
eq(0, #expected_handlers, "did not call expected handler") eq(0, #expected_handlers, "did not call expected handler")
end; end;
on_handler = function(err, _, ctx) on_handler = function(err, _, ctx)
@ -759,8 +759,8 @@ describe('LSP', function()
client.notify("release") client.notify("release")
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
eq(0, #expected_handlers, "did not call expected handler") eq(0, #expected_handlers, "did not call expected handler")
end; end;
on_handler = function(err, _, ctx) on_handler = function(err, _, ctx)
@ -793,8 +793,8 @@ describe('LSP', function()
client.notify("release") client.notify("release")
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
eq(0, #expected_handlers, "did not call expected handler") eq(0, #expected_handlers, "did not call expected handler")
end; end;
on_handler = function(err, _, ctx) on_handler = function(err, _, ctx)
@ -828,8 +828,8 @@ describe('LSP', function()
client.notify("release") client.notify("release")
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
eq(0, #expected_handlers, "did not call expected handler") eq(0, #expected_handlers, "did not call expected handler")
eq(3, eval('g:requests')) eq(3, eval('g:requests'))
end; end;
@ -874,8 +874,8 @@ describe('LSP', function()
client.notify('finish') client.notify('finish')
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
@ -917,8 +917,8 @@ describe('LSP', function()
]] ]]
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
if ctx.method == 'start' then if ctx.method == 'start' then
@ -960,8 +960,8 @@ describe('LSP', function()
]] ]]
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
if ctx.method == 'start' then if ctx.method == 'start' then
@ -1003,8 +1003,8 @@ describe('LSP', function()
]] ]]
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
if ctx.method == 'start' then if ctx.method == 'start' then
@ -1052,8 +1052,8 @@ describe('LSP', function()
]] ]]
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
if ctx.method == 'start' then if ctx.method == 'start' then
@ -1103,8 +1103,8 @@ describe('LSP', function()
]] ]]
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
if ctx.method == 'start' then if ctx.method == 'start' then
@ -1154,8 +1154,8 @@ describe('LSP', function()
]] ]]
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
if ctx.method == 'start' then if ctx.method == 'start' then
@ -1203,8 +1203,8 @@ describe('LSP', function()
]] ]]
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
if ctx.method == 'start' then if ctx.method == 'start' then
@ -1247,8 +1247,8 @@ describe('LSP', function()
]] ]]
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
if ctx.method == 'start' then if ctx.method == 'start' then
@ -1298,8 +1298,8 @@ describe('LSP', function()
]] ]]
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result,ctx) on_handler = function(err, result,ctx)
if ctx.method == 'start' then if ctx.method == 'start' then
@ -1340,8 +1340,8 @@ describe('LSP', function()
client.stop(true) client.stop(true)
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
@ -1379,8 +1379,8 @@ describe('LSP', function()
]] ]]
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
@ -1725,8 +1725,8 @@ describe('LSP', function()
end; end;
-- If the program timed out, then code will be nil. -- If the program timed out, then code will be nil.
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
-- Note that NIL must be used here. -- Note that NIL must be used here.
-- on_handler(err, method, result, client_id) -- on_handler(err, method, result, client_id)
@ -2728,8 +2728,8 @@ describe('LSP', function()
]=]) ]=])
end; end;
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end; end;
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
-- Don't compare & assert params, they're not relevant for the testcase -- Don't compare & assert params, they're not relevant for the testcase
@ -2768,8 +2768,8 @@ describe('LSP', function()
on_setup = function() on_setup = function()
end, end,
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end, end,
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}) eq(table.remove(expected_handlers), {err, result, ctx})
@ -2846,8 +2846,8 @@ describe('LSP', function()
on_setup = function() on_setup = function()
end, end,
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end, end,
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}) eq(table.remove(expected_handlers), {err, result, ctx})
@ -2919,8 +2919,8 @@ describe('LSP', function()
on_setup = function() on_setup = function()
end, end,
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end, end,
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}) eq(table.remove(expected_handlers), {err, result, ctx})
@ -2985,8 +2985,8 @@ describe('LSP', function()
]=]) ]=])
end, end,
on_exit = function(code, signal) on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile) eq(0, code, "exit code")
eq(0, signal, "exit signal", fake_lsp_logfile) eq(0, signal, "exit signal")
end, end,
on_handler = function(err, result, ctx) on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}) eq(table.remove(expected_handlers), {err, result, ctx})

View File

@ -1,8 +1,9 @@
-- Modules loaded here will not be cleared and reloaded by Busted. -- Modules loaded here will NOT be cleared and reloaded by Busted.
-- Busted started doing this to help provide more isolation. See issue #62 -- Busted started doing this to help provide more isolation. See issue #62
-- for more information about this. -- for more information about this.
local helpers = require('test.functional.helpers')(nil) local helpers = require('test.functional.helpers')(nil)
local iswin = helpers.iswin local iswin = helpers.iswin
local busted = require("busted")
if iswin() then if iswin() then
local ffi = require('ffi') local ffi = require('ffi')
@ -12,3 +13,28 @@ if iswin() then
]] ]]
ffi.C._set_fmode(0x8000) ffi.C._set_fmode(0x8000)
end end
local testid = (function()
local id = 0
return (function()
id = id + 1
return id
end)
end)()
-- Global before_each. https://github.com/Olivine-Labs/busted/issues/613
local function before_each(_element, _parent)
local id = ('T%d'):format(testid())
_G._nvim_test_id = id
return nil, true
end
busted.subscribe({ 'test', 'start' },
before_each,
{
-- Ensure our --helper is handled before --output (see busted/runner.lua).
priority = 1,
-- Don't generate a test-id for skipped tests. /shrug
predicate = function (element, _, status)
return not ((element.descriptor == 'pending' or status == 'pending'))
end
})

View File

@ -6,7 +6,7 @@ if helpers.pending_win32(pending) then return end
describe('api', function() describe('api', function()
local screen local screen
local socket_name = "Xtest_functional_api.sock" local socket_name = "./Xtest_functional_api.sock"
before_each(function() before_each(function()
helpers.clear() helpers.clear()
@ -29,7 +29,7 @@ describe('api', function()
{4:~ }| {4:~ }|
{4:~ }| {4:~ }|
{4:~ }| {4:~ }|
]]..socket_name..[[ | ]]..socket_name..[[ |
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]]) ]])

View File

@ -47,33 +47,33 @@ describe(':let', function()
end) end)
it("multibyte env var #8398 #9267", function() it("multibyte env var #8398 #9267", function()
command("let $NVIM_TEST = 'AìaB'") command("let $NVIM_TEST_LET = 'AìaB'")
eq('AìaB', eval('$NVIM_TEST')) eq('AìaB', eval('$NVIM_TEST_LET'))
command("let $NVIM_TEST = 'AaあB'") command("let $NVIM_TEST_LET = 'AaあB'")
eq('AaあB', eval('$NVIM_TEST')) eq('AaあB', eval('$NVIM_TEST_LET'))
local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]] .ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]]
command("let $NVIM_TEST = '"..mbyte.."'") command("let $NVIM_TEST_LET = '"..mbyte.."'")
eq(mbyte, eval('$NVIM_TEST')) eq(mbyte, eval('$NVIM_TEST_LET'))
end) end)
it("multibyte env var to child process #8398 #9267", function() it("multibyte env var to child process #8398 #9267", function()
local cmd_get_child_env = "let g:env_from_child = system(['"..nvim_dir.."/printenv-test', 'NVIM_TEST'])" local cmd_get_child_env = "let g:env_from_child = system(['"..nvim_dir.."/printenv-test', 'NVIM_TEST_LET'])"
command("let $NVIM_TEST = 'AìaB'") command("let $NVIM_TEST_LET = 'AìaB'")
command(cmd_get_child_env) command(cmd_get_child_env)
eq(eval('$NVIM_TEST'), eval('g:env_from_child')) eq(eval('$NVIM_TEST_LET'), eval('g:env_from_child'))
command("let $NVIM_TEST = 'AaあB'") command("let $NVIM_TEST_LET = 'AaあB'")
command(cmd_get_child_env) command(cmd_get_child_env)
eq(eval('$NVIM_TEST'), eval('g:env_from_child')) eq(eval('$NVIM_TEST_LET'), eval('g:env_from_child'))
local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]] .ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]]
command("let $NVIM_TEST = '"..mbyte.."'") command("let $NVIM_TEST_LET = '"..mbyte.."'")
command(cmd_get_child_env) command(cmd_get_child_env)
eq(eval('$NVIM_TEST'), eval('g:env_from_child')) eq(eval('$NVIM_TEST_LET'), eval('g:env_from_child'))
end) end)
it("release of list assigned to l: variable does not trigger assertion #12387, #12430", function() it("release of list assigned to l: variable does not trigger assertion #12387, #12430", function()

View File

@ -30,7 +30,7 @@ describe('server', function()
eq('', eval('$NVIM_LISTEN_ADDRESS')) eq('', eval('$NVIM_LISTEN_ADDRESS'))
local servers = funcs.serverlist() local servers = funcs.serverlist()
eq(1, #servers) eq(1, #servers)
ok(string.len(servers[1]) > 4) -- Like /tmp/nvim…/… or \\.\pipe\… ok(string.len(servers[1]) > 4) -- "~/.local/state/nvim…/…" or "\\.\pipe\…"
end) end)
it('sets v:servername at startup or if all servers were stopped', function() it('sets v:servername at startup or if all servers were stopped', function()
@ -54,7 +54,7 @@ describe('server', function()
-- v:servername and $NVIM take the next available server. -- v:servername and $NVIM take the next available server.
local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]] local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]]
or 'Xtest-functional-server-socket') or './Xtest-functional-server-socket')
funcs.serverstart(servername) funcs.serverstart(servername)
eq(servername, meths.get_vvar('servername')) eq(servername, meths.get_vvar('servername'))
-- Not set in the current process, only in children. -- Not set in the current process, only in children.
@ -66,7 +66,7 @@ describe('server', function()
eq(0, eval("serverstop('bogus-socket-name')")) eq(0, eval("serverstop('bogus-socket-name')"))
end) end)
it('parses endpoints correctly', function() it('parses endpoints', function()
clear_serverlist() clear_serverlist()
eq({}, funcs.serverlist()) eq({}, funcs.serverlist())
@ -101,6 +101,10 @@ describe('server', function()
eq(expected, funcs.serverlist()) eq(expected, funcs.serverlist())
clear_serverlist() clear_serverlist()
-- Address without slashes is a "name" which is appended to a generated path. #8519
matches([[.*[/\\]xtest1%.2%.3%.4[^/\\]*]], funcs.serverstart('xtest1.2.3.4'))
clear_serverlist()
eq('Vim:Failed to start server: invalid argument', eq('Vim:Failed to start server: invalid argument',
pcall_err(funcs.serverstart, '127.0.0.1:65536')) -- invalid port pcall_err(funcs.serverstart, '127.0.0.1:65536')) -- invalid port
eq({}, funcs.serverlist()) eq({}, funcs.serverlist())
@ -113,7 +117,7 @@ describe('server', function()
-- Add some servers. -- Add some servers.
local servs = (iswin() local servs = (iswin()
and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] } and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] }
or { [[Xtest-pipe0934]], [[Xtest-pipe4324]] }) or { [[./Xtest-pipe0934]], [[./Xtest-pipe4324]] })
for _, s in ipairs(servs) do for _, s in ipairs(servs) do
eq(s, eval("serverstart('"..s.."')")) eq(s, eval("serverstart('"..s.."')"))
end end
@ -146,9 +150,13 @@ describe('startup --listen', function()
it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function()
local addr = (iswin() and [[\\.\pipe\Xtest-listen-pipe]] local addr = (iswin() and [[\\.\pipe\Xtest-listen-pipe]]
or 'Xtest-listen-pipe') or './Xtest-listen-pipe')
clear({ env={ NVIM_LISTEN_ADDRESS='Xtest-env-pipe' }, clear({ env={ NVIM_LISTEN_ADDRESS='./Xtest-env-pipe' },
args={ '--listen', addr } }) args={ '--listen', addr } })
eq(addr, meths.get_vvar('servername')) eq(addr, meths.get_vvar('servername'))
-- Address without slashes is a "name" which is appended to a generated path. #8519
clear({ args={ '--listen', 'test-name' } })
matches([[.*[/\\]test%-name[^/\\]*]], meths.get_vvar('servername'))
end) end)
end) end)

View File

@ -40,10 +40,6 @@ function module.popen_r(...)
return io.popen(module.argss_to_cmd(...), 'r') return io.popen(module.argss_to_cmd(...), 'r')
end end
function module.popen_w(...)
return io.popen(module.argss_to_cmd(...), 'w')
end
-- sleeps the test runner (_not_ the nvim instance) -- sleeps the test runner (_not_ the nvim instance)
function module.sleep(ms) function module.sleep(ms)
luv.sleep(ms) luv.sleep(ms)
@ -55,42 +51,23 @@ local check_logs_useless_lines = {
['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3, ['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3,
} }
--- Invokes `fn` and includes the tail of `logfile` in the error message if it function module.eq(expected, actual, context)
--- fails. return assert.are.same(expected, actual, context)
---
---@param logfile string Log file, defaults to $NVIM_LOG_FILE or '.nvimlog'
---@param fn string Function to invoke
---@param ... string Function arguments
local function dumplog(logfile, fn, ...)
-- module.validate({
-- logfile={logfile,'s',true},
-- fn={fn,'f',false},
-- })
local status, rv = pcall(fn, ...)
if status == false then
logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog'
local logtail = module.read_nvim_log(logfile)
error(string.format('%s\n%s', tostring(rv), logtail))
end
end end
function module.eq(expected, actual, context, logfile) function module.neq(expected, actual, context)
return dumplog(logfile, assert.are.same, expected, actual, context) return assert.are_not.same(expected, actual, context)
end end
function module.neq(expected, actual, context, logfile) function module.ok(res, msg)
return dumplog(logfile, assert.are_not.same, expected, actual, context) return assert.is_true(res, msg)
end
function module.ok(res, msg, logfile)
return dumplog(logfile, assert.is_true, res, msg)
end end
-- TODO(bfredl): this should "failure" not "error" (issue with dumplog() )
local function epicfail(state, arguments, _) local function epicfail(state, arguments, _)
state.failure_message = arguments[1] state.failure_message = arguments[1]
return false return false
end end
assert:register("assertion", "epicfail", epicfail) assert:register("assertion", "epicfail", epicfail)
function module.fail(msg, logfile) function module.fail(msg)
return dumplog(logfile, assert.epicfail, msg) return assert.epicfail(msg)
end end
function module.matches(pat, actual) function module.matches(pat, actual)
@ -104,16 +81,16 @@ end
--- ---
---@param pat string Lua pattern to search for in the log file ---@param pat string Lua pattern to search for in the log file
---@param logfile string Full path to log file (default=$NVIM_LOG_FILE) ---@param logfile string Full path to log file (default=$NVIM_LOG_FILE)
function module.assert_log(pat, logfile) ---@param nrlines number Search up to this many log lines
function module.assert_log(pat, logfile, nrlines)
logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog'
local nrlines = 10 nrlines = nrlines or 10
local lines = module.read_file_list(logfile, -nrlines) or {} local lines = module.read_file_list(logfile, -nrlines) or {}
for _,line in ipairs(lines) do for _,line in ipairs(lines) do
if line:match(pat) then return end if line:match(pat) then return end
end end
local logtail = module.read_nvim_log(logfile)
error(string.format('Pattern %q not found in log (last %d lines): %s:\n%s', error(string.format('Pattern %q not found in log (last %d lines): %s:\n%s',
pat, nrlines, logfile, logtail)) pat, nrlines, logfile, ' '..table.concat(lines, '\n ')))
end end
-- Invokes `fn` and returns the error string (with truncated paths), or raises -- Invokes `fn` and returns the error string (with truncated paths), or raises
@ -271,7 +248,7 @@ module.uname = (function()
return platform return platform
end end
if os.getenv("SYSTEM_NAME") then -- From CMAKE_SYSTEM_NAME. if os.getenv("SYSTEM_NAME") then -- From CMAKE_HOST_SYSTEM_NAME.
platform = string.lower(os.getenv("SYSTEM_NAME")) platform = string.lower(os.getenv("SYSTEM_NAME"))
return platform return platform
end end
@ -409,17 +386,6 @@ function module.check_cores(app, force)
end end
end end
function module.which(exe)
local pipe = module.popen_r('which', exe)
local ret = pipe:read('*a')
pipe:close()
if ret == '' then
return nil
else
return ret:sub(1, -2)
end
end
function module.repeated_read_cmd(...) function module.repeated_read_cmd(...)
for _ = 1, 10 do for _ = 1, 10 do
local stream = module.popen_r(...) local stream = module.popen_r(...)