neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit 8a2aec99748229ad9d1e12c1cbc0768d063e8eed
parent 3a881132460430d23f2fdc87822c87d47f721cfc
Author: Justin M. Keyes <justinkz@gmail.com>
Date:   Sun,  8 Sep 2024 12:48:32 -0700

fix(startup): server fails if $NVIM_APPNAME is relative dir #30310

Problem:
If $NVIM_APPNAME is a relative dir path, Nvim fails to start its
primary/default server, and `v:servername` is empty.
Root cause is d34c64e342dfba9248d1055e702d02620a1b31a8, but this wasn't
noticed until 96128a5076b7 started reporting the error more loudly.

Solution:
- `server_address_new`: replace slashes "/" in the appname before using
  it as a servername.
- `vim_mktempdir`: always prefer the system-wide top-level "nvim.user/"
  directory. That isn't intended to be specific to NVIM_APPNAME; rather,
  each *subdirectory* ("nvim.user/xxx") is owned by each Nvim instance.
  Nvim "apps" can be identified by the server socket(s) stored in those
  per-Nvim subdirs.

fix #30256
Diffstat:
Msrc/nvim/eval/funcs.c | 2+-
Msrc/nvim/fileio.c | 13++++---------
Msrc/nvim/msgpack_rpc/server.c | 7++++---
Msrc/nvim/os/stdpaths.c | 18++++++++++++++----
Msrc/nvim/runtime.c | 6+++---
Mtest/functional/core/fileio_spec.lua | 15++-------------
Atest/functional/core/server_spec.lua | 278+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/functional/options/defaults_spec.lua | 21++++++++++++++++++++-
Mtest/functional/testnvim.lua | 3+--
Dtest/functional/vimscript/server_spec.lua | 278-------------------------------------------------------------------------------
Mtest/testutil.lua | 2+-
11 files changed, 328 insertions(+), 315 deletions(-)

diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c @@ -7641,7 +7641,7 @@ static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) return; } const void *iter = NULL; - const char *appname = get_appname(); + const char *appname = get_appname(false); do { size_t dir_len; const char *dir; diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c @@ -3264,18 +3264,12 @@ static void vim_mktempdir(void) char tmp[TEMP_FILE_PATH_MAXLEN]; char path[TEMP_FILE_PATH_MAXLEN]; char user[40] = { 0 }; - char appname[40] = { 0 }; os_get_username(user, sizeof(user)); // Usernames may contain slashes! #19240 memchrsub(user, '/', '_', sizeof(user)); memchrsub(user, '\\', '_', sizeof(user)); - // Appname may be a relative path, replace slashes to make it name-like. - xstrlcpy(appname, get_appname(), sizeof(appname)); - memchrsub(appname, '/', '%', sizeof(appname)); - memchrsub(appname, '\\', '%', sizeof(appname)); - // Make sure the umask doesn't remove the executable bit. // "repl" has been reported to use "0177". mode_t umask_save = umask(0077); @@ -3283,14 +3277,15 @@ static void vim_mktempdir(void) // Expand environment variables, leave room for "/tmp/nvim.<user>/XXXXXX/999999999". expand_env((char *)temp_dirs[i], tmp, TEMP_FILE_PATH_MAXLEN - 64); if (!os_isdir(tmp)) { + if (strequal("$TMPDIR", temp_dirs[i])) { + WLOG("$TMPDIR tempdir not a directory (or does not exist): %s", tmp); + } continue; } // "/tmp/" exists, now try to create "/tmp/nvim.<user>/". add_pathsep(tmp); - - xstrlcat(tmp, appname, sizeof(tmp)); - xstrlcat(tmp, ".", sizeof(tmp)); + xstrlcat(tmp, "nvim.", sizeof(tmp)); xstrlcat(tmp, user, sizeof(tmp)); os_mkdir(tmp, 0700); // Always create, to avoid a race. bool owned = os_file_owned(tmp); diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c @@ -121,14 +121,15 @@ char *server_address_new(const char *name) { static uint32_t count = 0; char fmt[ADDRESS_MAX_SIZE]; - const char *appname = get_appname(); #ifdef MSWIN + (void)get_appname(true); int r = snprintf(fmt, sizeof(fmt), "\\\\.\\pipe\\%s.%" PRIu64 ".%" PRIu32, - name ? name : appname, os_get_pid(), count++); + name ? name : NameBuff, os_get_pid(), count++); #else char *dir = stdpaths_get_xdg_var(kXDGRuntimeDir); + (void)get_appname(true); int r = snprintf(fmt, sizeof(fmt), "%s/%s.%" PRIu64 ".%" PRIu32, - dir, name ? name : appname, os_get_pid(), count++); + dir, name ? name : NameBuff, os_get_pid(), count++); xfree(dir); #endif if ((size_t)r >= sizeof(fmt)) { diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c @@ -63,22 +63,32 @@ static const char *const xdg_defaults[] = { #endif }; -/// Get the value of $NVIM_APPNAME or "nvim" if not set. +/// Gets the value of $NVIM_APPNAME, or "nvim" if not set. +/// +/// @param namelike Write "name-like" value (no path separators) in `NameBuff`. /// /// @return $NVIM_APPNAME value -const char *get_appname(void) +const char *get_appname(bool namelike) { const char *env_val = os_getenv("NVIM_APPNAME"); if (env_val == NULL || *env_val == NUL) { env_val = "nvim"; } + + if (namelike) { + // Appname may be a relative path, replace slashes to make it name-like. + xstrlcpy(NameBuff, env_val, sizeof(NameBuff)); + memchrsub(NameBuff, '/', '-', sizeof(NameBuff)); + memchrsub(NameBuff, '\\', '-', sizeof(NameBuff)); + } + return env_val; } /// Ensure that APPNAME is valid. Must be a name or relative path. bool appname_is_valid(void) { - const char *appname = get_appname(); + const char *appname = get_appname(false); if (path_is_absolute(appname) // TODO(justinmk): on Windows, path_is_absolute says "/" is NOT absolute. Should it? || strequal(appname, "/") @@ -193,7 +203,7 @@ char *get_xdg_home(const XDGVarType idx) FUNC_ATTR_WARN_UNUSED_RESULT { char *dir = stdpaths_get_xdg_var(idx); - const char *appname = get_appname(); + const char *appname = get_appname(false); size_t appname_len = strlen(appname); assert(appname_len < (IOSIZE - sizeof("-data"))); diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c @@ -1559,7 +1559,7 @@ static inline char *add_env_sep_dirs(char *dest, const char *const val, const ch return dest; } const void *iter = NULL; - const char *appname = get_appname(); + const char *appname = get_appname(false); const size_t appname_len = strlen(appname); do { size_t dir_len; @@ -1626,7 +1626,7 @@ static inline char *add_dir(char *dest, const char *const dir, const size_t dir_ if (!after_pathsep(dest - 1, dest)) { *dest++ = PATHSEP; } - const char *appname = get_appname(); + const char *appname = get_appname(false); size_t appname_len = strlen(appname); assert(appname_len < (IOSIZE - sizeof("-data"))); xmemcpyz(IObuff, appname, appname_len); @@ -1697,7 +1697,7 @@ char *runtimepath_default(bool clean_arg) size_t config_len = 0; size_t vimruntime_len = 0; size_t libdir_len = 0; - const char *appname = get_appname(); + const char *appname = get_appname(false); size_t appname_len = strlen(appname); if (data_home != NULL) { data_len = strlen(data_home); diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua @@ -321,11 +321,11 @@ end) describe('tmpdir', function() local tmproot_pat = [=[.*[/\\]nvim%.[^/\\]+]=] local testlog = 'Xtest_tmpdir_log' - local os_tmpdir + local os_tmpdir ---@type string before_each(function() -- Fake /tmp dir so that we can mess it up. - os_tmpdir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX') + os_tmpdir = assert(vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX')) end) after_each(function() @@ -414,15 +414,4 @@ describe('tmpdir', function() rm_tmpdir() eq('E5431: tempdir disappeared (3 times)', api.nvim_get_vvar('errmsg')) end) - - it('$NVIM_APPNAME relative path', function() - clear({ - env = { - NVIM_APPNAME = 'a/b', - NVIM_LOG_FILE = testlog, - TMPDIR = os_tmpdir, - }, - }) - matches([=[.*[/\\]a%%b%.[^/\\]+]=], fn.tempname()) - end) end) diff --git a/test/functional/core/server_spec.lua b/test/functional/core/server_spec.lua @@ -0,0 +1,278 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local eq, neq, eval = t.eq, t.neq, n.eval +local clear, fn, api = n.clear, n.fn, n.api +local matches = t.matches +local pcall_err = t.pcall_err +local check_close = n.check_close +local mkdir = t.mkdir +local rmdir = n.rmdir +local is_os = t.is_os + +local testlog = 'Xtest-server-log' + +local function clear_serverlist() + for _, server in pairs(fn.serverlist()) do + fn.serverstop(server) + end +end + +after_each(function() + check_close() + os.remove(testlog) +end) + +before_each(function() + os.remove(testlog) +end) + +describe('server', function() + it('serverstart() stores sockets in $XDG_RUNTIME_DIR', function() + local dir = 'Xtest_xdg_run' + mkdir(dir) + finally(function() + rmdir(dir) + end) + clear({ env = { XDG_RUNTIME_DIR = dir } }) + matches(dir, fn.stdpath('run')) + if not is_os('win') then + matches(dir, fn.serverstart()) + end + end) + + it('broken $XDG_RUNTIME_DIR is not fatal #30282', function() + clear { + args_rm = { '--listen' }, + env = { NVIM_LOG_FILE = testlog, XDG_RUNTIME_DIR = '/non-existent-dir/subdir//' }, + } + + if is_os('win') then + -- Windows pipes have a special namespace and thus aren't decided by $XDG_RUNTIME_DIR. + matches('nvim', api.nvim_get_vvar('servername')) + else + eq('', api.nvim_get_vvar('servername')) + t.assert_log('Failed to start server%: no such file or directory', testlog, 100) + end + end) + + it('serverstart(), serverstop() does not set $NVIM', function() + clear() + local s = eval('serverstart()') + assert(s ~= nil and s:len() > 0, 'serverstart() returned empty') + eq('', eval('$NVIM')) + eq('', eval('$NVIM_LISTEN_ADDRESS')) + eq(1, eval("serverstop('" .. s .. "')")) + eq('', eval('$NVIM_LISTEN_ADDRESS')) + end) + + it('sets v:servername at startup or if all servers were stopped', function() + clear() + local initial_server = api.nvim_get_vvar('servername') + assert(initial_server ~= nil and initial_server:len() > 0, 'v:servername was not initialized') + + -- v:servername is readonly so we cannot unset it--but we can test that it + -- does not get set again thereafter. + local s = fn.serverstart() + assert(s ~= nil and s:len() > 0, 'serverstart() returned empty') + neq(initial_server, s) + + -- serverstop() does _not_ modify v:servername... + eq(1, fn.serverstop(s)) + eq(initial_server, api.nvim_get_vvar('servername')) + + -- ...unless we stop _all_ servers. + eq(1, fn.serverstop(fn.serverlist()[1])) + eq('', api.nvim_get_vvar('servername')) + + -- v:servername and $NVIM take the next available server. + local servername = ( + is_os('win') and [[\\.\pipe\Xtest-functional-server-pipe]] + or './Xtest-functional-server-socket' + ) + fn.serverstart(servername) + eq(servername, api.nvim_get_vvar('servername')) + -- Not set in the current process, only in children. + eq('', eval('$NVIM')) + end) + + it('serverstop() returns false for invalid input', function() + clear { + args_rm = { '--listen' }, + env = { + NVIM_LOG_FILE = testlog, + NVIM_LISTEN_ADDRESS = '', + }, + } + eq(0, eval("serverstop('')")) + eq(0, eval("serverstop('bogus-socket-name')")) + t.assert_log('Not listening on bogus%-socket%-name', testlog, 10) + end) + + it('parses endpoints', function() + clear { + args_rm = { '--listen' }, + env = { + NVIM_LOG_FILE = testlog, + NVIM_LISTEN_ADDRESS = '', + }, + } + clear_serverlist() + eq({}, fn.serverlist()) + + local s = fn.serverstart('127.0.0.1:0') -- assign random port + if #s > 0 then + matches('127.0.0.1:%d+', s) + eq(s, fn.serverlist()[1]) + clear_serverlist() + end + + s = fn.serverstart('127.0.0.1:') -- assign random port + if #s > 0 then + matches('127.0.0.1:%d+', s) + eq(s, fn.serverlist()[1]) + clear_serverlist() + end + + local expected = {} + local v4 = '127.0.0.1:12345' + local status, _ = pcall(fn.serverstart, v4) + if status then + table.insert(expected, v4) + pcall(fn.serverstart, v4) -- exists already; ignore + t.assert_log('Failed to start server: address already in use: 127%.0%.0%.1', testlog, 10) + end + + local v6 = '::1:12345' + status, _ = pcall(fn.serverstart, v6) + if status then + table.insert(expected, v6) + pcall(fn.serverstart, v6) -- exists already; ignore + t.assert_log('Failed to start server: address already in use: ::1', testlog, 10) + end + eq(expected, fn.serverlist()) + clear_serverlist() + + -- Address without slashes is a "name" which is appended to a generated path. #8519 + matches([[[/\\]xtest1%.2%.3%.4[^/\\]*]], fn.serverstart('xtest1.2.3.4')) + clear_serverlist() + + eq('Vim:Failed to start server: invalid argument', pcall_err(fn.serverstart, '127.0.0.1:65536')) -- invalid port + eq({}, fn.serverlist()) + end) + + it('serverlist() returns the list of servers', function() + clear() + -- There should already be at least one server. + local _n = eval('len(serverlist())') + + -- Add some servers. + local servs = ( + is_os('win') and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] } + or { [[./Xtest-pipe0934]], [[./Xtest-pipe4324]] } + ) + for _, s in ipairs(servs) do + eq(s, eval("serverstart('" .. s .. "')")) + end + + local new_servs = eval('serverlist()') + + -- Exactly #servs servers should be added. + eq(_n + #servs, #new_servs) + -- The new servers should be at the end of the list. + for i = 1, #servs do + eq(servs[i], new_servs[i + _n]) + eq(1, eval("serverstop('" .. servs[i] .. "')")) + end + -- After serverstop() the servers should NOT be in the list. + eq(_n, eval('len(serverlist())')) + end) +end) + +describe('startup --listen', function() + -- Tests Nvim output when failing to start, with and without "--headless". + -- TODO(justinmk): clear() should have a way to get stdout if Nvim fails to start. + local function _test(args, env, expected) + local function run(cmd) + return n.exec_lua(function(cmd_, env_) + return vim + .system(cmd_, { + text = true, + env = vim.tbl_extend( + 'force', + -- Avoid noise in the logs; we expect failures for these tests. + { NVIM_LOG_FILE = testlog }, + env_ or {} + ), + }) + :wait() + end, cmd, env) --[[@as vim.SystemCompleted]] + end + + local cmd = vim.list_extend({ n.nvim_prog, '+qall!', '--headless' }, args) + local r = run(cmd) + eq(1, r.code) + matches(expected, (r.stderr .. r.stdout):gsub('\\n', ' ')) + + if is_os('win') then + return -- On Windows, output without --headless is garbage. + end + table.remove(cmd, 3) -- Remove '--headless'. + assert(not vim.tbl_contains(cmd, '--headless')) + r = run(cmd) + eq(1, r.code) + matches(expected, (r.stderr .. r.stdout):gsub('\\n', ' ')) + end + + it('validates', function() + clear { env = { NVIM_LOG_FILE = testlog } } + local in_use = n.eval('v:servername') ---@type string Address already used by another server. + + t.assert_nolog('Failed to start server', testlog, 100) + t.assert_nolog('Host lookup failed', testlog, 100) + + _test({ '--listen' }, nil, 'nvim.*: Argument missing after: "%-%-listen"') + _test({ '--listen2' }, nil, 'nvim.*: Garbage after option argument: "%-%-listen2"') + _test( + { '--listen', in_use }, + nil, + ('nvim.*: Failed to %%-%%-listen: [^:]+ already [^:]+: "%s"'):format(vim.pesc(in_use)) + ) + _test({ '--listen', '/' }, nil, 'nvim.*: Failed to %-%-listen: [^:]+: "/"') + _test( + { '--listen', 'https://example.com' }, + nil, + ('nvim.*: Failed to %%-%%-listen: %s: "https://example.com"'):format( + is_os('mac') and 'unknown node or service' or 'service not available for socket type' + ) + ) + + t.assert_log('Failed to start server', testlog, 100) + t.assert_log('Host lookup failed', testlog, 100) + + _test( + {}, + { NVIM_LISTEN_ADDRESS = in_use }, + ('nvim.*: Failed $NVIM_LISTEN_ADDRESS: [^:]+ already [^:]+: "%s"'):format(vim.pesc(in_use)) + ) + _test({}, { NVIM_LISTEN_ADDRESS = '/' }, 'nvim.*: Failed $NVIM_LISTEN_ADDRESS: [^:]+: "/"') + _test( + {}, + { NVIM_LISTEN_ADDRESS = 'https://example.com' }, + ('nvim.*: Failed $NVIM_LISTEN_ADDRESS: %s: "https://example.com"'):format( + is_os('mac') and 'unknown node or service' or 'service not available for socket type' + ) + ) + end) + + it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() + local addr = (is_os('win') and [[\\.\pipe\Xtest-listen-pipe]] or './Xtest-listen-pipe') + clear({ env = { NVIM_LISTEN_ADDRESS = './Xtest-env-pipe' }, args = { '--listen', addr } }) + eq('', eval('$NVIM_LISTEN_ADDRESS')) -- Cleared on startup. + eq(addr, api.nvim_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[^/\\]*]], api.nvim_get_vvar('servername')) + end) +end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua @@ -915,7 +915,7 @@ describe('stdpath()', function() assert_alive() -- Check for crash. #8393 end) - it('supports $NVIM_APPNAME', function() + it('$NVIM_APPNAME', function() local appname = 'NVIM_APPNAME_TEST' .. ('_'):rep(106) clear({ env = { NVIM_APPNAME = appname, NVIM_LOG_FILE = testlog } }) eq(appname, fn.fnamemodify(fn.stdpath('config'), ':t')) @@ -957,6 +957,25 @@ describe('stdpath()', function() test_appname('a/b\\c', 0) end) + it('$NVIM_APPNAME relative path', function() + local tmpdir = t.tmpname(false) + t.mkdir(tmpdir) + + clear({ + args_rm = { '--listen' }, + env = { + NVIM_APPNAME = 'relative/appname', + NVIM_LOG_FILE = testlog, + TMPDIR = tmpdir, + }, + }) + + t.matches(vim.pesc(tmpdir), fn.tempname():gsub('\\', '/')) + t.assert_nolog('tempdir', testlog, 100) + t.assert_nolog('TMPDIR', testlog, 100) + t.matches([=[[/\\]relative%-appname.[^/\\]+]=], api.nvim_get_vvar('servername')) + end) + describe('returns a String', function() describe('with "config"', function() it('knows XDG_CONFIG_HOME', function() diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua @@ -14,8 +14,7 @@ local is_os = t.is_os local ok = t.ok local sleep = uv.sleep ---- This module uses functions from the context of the test session, i.e. in the context of the ---- nvim being tests. +--- Functions executing in the current nvim session/process being tested. local M = {} local runtime_set = 'set runtimepath^=./build/lib/nvim/' diff --git a/test/functional/vimscript/server_spec.lua b/test/functional/vimscript/server_spec.lua @@ -1,278 +0,0 @@ -local t = require('test.testutil') -local n = require('test.functional.testnvim')() - -local eq, neq, eval = t.eq, t.neq, n.eval -local clear, fn, api = n.clear, n.fn, n.api -local matches = t.matches -local pcall_err = t.pcall_err -local check_close = n.check_close -local mkdir = t.mkdir -local rmdir = n.rmdir -local is_os = t.is_os - -local testlog = 'Xtest-server-log' - -local function clear_serverlist() - for _, server in pairs(fn.serverlist()) do - fn.serverstop(server) - end -end - -after_each(function() - check_close() - os.remove(testlog) -end) - -before_each(function() - os.remove(testlog) -end) - -describe('server', function() - it('serverstart() stores sockets in $XDG_RUNTIME_DIR', function() - local dir = 'Xtest_xdg_run' - mkdir(dir) - finally(function() - rmdir(dir) - end) - clear({ env = { XDG_RUNTIME_DIR = dir } }) - matches(dir, fn.stdpath('run')) - if not is_os('win') then - matches(dir, fn.serverstart()) - end - end) - - it('broken $XDG_RUNTIME_DIR is not fatal #30282', function() - clear { - args_rm = { '--listen' }, - env = { NVIM_LOG_FILE = testlog, XDG_RUNTIME_DIR = '/non-existent-dir/subdir//' }, - } - - if is_os('win') then - -- Windows pipes have a special namespace and thus aren't decided by $XDG_RUNTIME_DIR. - matches('nvim', api.nvim_get_vvar('servername')) - else - eq('', api.nvim_get_vvar('servername')) - t.assert_log('Failed to start server%: no such file or directory', testlog, 100) - end - end) - - it('serverstart(), serverstop() does not set $NVIM', function() - clear() - local s = eval('serverstart()') - assert(s ~= nil and s:len() > 0, 'serverstart() returned empty') - eq('', eval('$NVIM')) - eq('', eval('$NVIM_LISTEN_ADDRESS')) - eq(1, eval("serverstop('" .. s .. "')")) - eq('', eval('$NVIM_LISTEN_ADDRESS')) - end) - - it('sets v:servername at startup or if all servers were stopped', function() - clear() - local initial_server = api.nvim_get_vvar('servername') - assert(initial_server ~= nil and initial_server:len() > 0, 'v:servername was not initialized') - - -- v:servername is readonly so we cannot unset it--but we can test that it - -- does not get set again thereafter. - local s = fn.serverstart() - assert(s ~= nil and s:len() > 0, 'serverstart() returned empty') - neq(initial_server, s) - - -- serverstop() does _not_ modify v:servername... - eq(1, fn.serverstop(s)) - eq(initial_server, api.nvim_get_vvar('servername')) - - -- ...unless we stop _all_ servers. - eq(1, fn.serverstop(fn.serverlist()[1])) - eq('', api.nvim_get_vvar('servername')) - - -- v:servername and $NVIM take the next available server. - local servername = ( - is_os('win') and [[\\.\pipe\Xtest-functional-server-pipe]] - or './Xtest-functional-server-socket' - ) - fn.serverstart(servername) - eq(servername, api.nvim_get_vvar('servername')) - -- Not set in the current process, only in children. - eq('', eval('$NVIM')) - end) - - it('serverstop() returns false for invalid input', function() - clear { - args_rm = { '--listen' }, - env = { - NVIM_LOG_FILE = testlog, - NVIM_LISTEN_ADDRESS = '', - }, - } - eq(0, eval("serverstop('')")) - eq(0, eval("serverstop('bogus-socket-name')")) - t.assert_log('Not listening on bogus%-socket%-name', testlog, 10) - end) - - it('parses endpoints', function() - clear { - args_rm = { '--listen' }, - env = { - NVIM_LOG_FILE = testlog, - NVIM_LISTEN_ADDRESS = '', - }, - } - clear_serverlist() - eq({}, fn.serverlist()) - - local s = fn.serverstart('127.0.0.1:0') -- assign random port - if #s > 0 then - matches('127.0.0.1:%d+', s) - eq(s, fn.serverlist()[1]) - clear_serverlist() - end - - s = fn.serverstart('127.0.0.1:') -- assign random port - if #s > 0 then - matches('127.0.0.1:%d+', s) - eq(s, fn.serverlist()[1]) - clear_serverlist() - end - - local expected = {} - local v4 = '127.0.0.1:12345' - local status, _ = pcall(fn.serverstart, v4) - if status then - table.insert(expected, v4) - pcall(fn.serverstart, v4) -- exists already; ignore - t.assert_log('Failed to start server: address already in use: 127%.0%.0%.1', testlog, 10) - end - - local v6 = '::1:12345' - status, _ = pcall(fn.serverstart, v6) - if status then - table.insert(expected, v6) - pcall(fn.serverstart, v6) -- exists already; ignore - t.assert_log('Failed to start server: address already in use: ::1', testlog, 10) - end - eq(expected, fn.serverlist()) - clear_serverlist() - - -- Address without slashes is a "name" which is appended to a generated path. #8519 - matches([[.*[/\\]xtest1%.2%.3%.4[^/\\]*]], fn.serverstart('xtest1.2.3.4')) - clear_serverlist() - - eq('Vim:Failed to start server: invalid argument', pcall_err(fn.serverstart, '127.0.0.1:65536')) -- invalid port - eq({}, fn.serverlist()) - end) - - it('serverlist() returns the list of servers', function() - clear() - -- There should already be at least one server. - local _n = eval('len(serverlist())') - - -- Add some servers. - local servs = ( - is_os('win') and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] } - or { [[./Xtest-pipe0934]], [[./Xtest-pipe4324]] } - ) - for _, s in ipairs(servs) do - eq(s, eval("serverstart('" .. s .. "')")) - end - - local new_servs = eval('serverlist()') - - -- Exactly #servs servers should be added. - eq(_n + #servs, #new_servs) - -- The new servers should be at the end of the list. - for i = 1, #servs do - eq(servs[i], new_servs[i + _n]) - eq(1, eval("serverstop('" .. servs[i] .. "')")) - end - -- After serverstop() the servers should NOT be in the list. - eq(_n, eval('len(serverlist())')) - end) -end) - -describe('startup --listen', function() - -- Tests Nvim output when failing to start, with and without "--headless". - -- TODO(justinmk): clear() should have a way to get stdout if Nvim fails to start. - local function _test(args, env, expected) - local function run(cmd) - return n.exec_lua(function(cmd_, env_) - return vim - .system(cmd_, { - text = true, - env = vim.tbl_extend( - 'force', - -- Avoid noise in the logs; we expect failures for these tests. - { NVIM_LOG_FILE = testlog }, - env_ or {} - ), - }) - :wait() - end, cmd, env) --[[@as vim.SystemCompleted]] - end - - local cmd = vim.list_extend({ n.nvim_prog, '+qall!', '--headless' }, args) - local r = run(cmd) - eq(1, r.code) - matches(expected, (r.stderr .. r.stdout):gsub('\\n', ' ')) - - if is_os('win') then - return -- On Windows, output without --headless is garbage. - end - table.remove(cmd, 3) -- Remove '--headless'. - assert(not vim.tbl_contains(cmd, '--headless')) - r = run(cmd) - eq(1, r.code) - matches(expected, (r.stderr .. r.stdout):gsub('\\n', ' ')) - end - - it('validates', function() - clear { env = { NVIM_LOG_FILE = testlog } } - local in_use = n.eval('v:servername') ---@type string Address already used by another server. - - t.assert_nolog('Failed to start server', testlog, 100) - t.assert_nolog('Host lookup failed', testlog, 100) - - _test({ '--listen' }, nil, 'nvim.*: Argument missing after: "%-%-listen"') - _test({ '--listen2' }, nil, 'nvim.*: Garbage after option argument: "%-%-listen2"') - _test( - { '--listen', in_use }, - nil, - ('nvim.*: Failed to %%-%%-listen: [^:]+ already [^:]+: "%s"'):format(vim.pesc(in_use)) - ) - _test({ '--listen', '/' }, nil, 'nvim.*: Failed to %-%-listen: [^:]+: "/"') - _test( - { '--listen', 'https://example.com' }, - nil, - ('nvim.*: Failed to %%-%%-listen: %s: "https://example.com"'):format( - is_os('mac') and 'unknown node or service' or 'service not available for socket type' - ) - ) - - t.assert_log('Failed to start server', testlog, 100) - t.assert_log('Host lookup failed', testlog, 100) - - _test( - {}, - { NVIM_LISTEN_ADDRESS = in_use }, - ('nvim.*: Failed $NVIM_LISTEN_ADDRESS: [^:]+ already [^:]+: "%s"'):format(vim.pesc(in_use)) - ) - _test({}, { NVIM_LISTEN_ADDRESS = '/' }, 'nvim.*: Failed $NVIM_LISTEN_ADDRESS: [^:]+: "/"') - _test( - {}, - { NVIM_LISTEN_ADDRESS = 'https://example.com' }, - ('nvim.*: Failed $NVIM_LISTEN_ADDRESS: %s: "https://example.com"'):format( - is_os('mac') and 'unknown node or service' or 'service not available for socket type' - ) - ) - end) - - it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() - local addr = (is_os('win') and [[\\.\pipe\Xtest-listen-pipe]] or './Xtest-listen-pipe') - clear({ env = { NVIM_LISTEN_ADDRESS = './Xtest-env-pipe' }, args = { '--listen', addr } }) - eq('', eval('$NVIM_LISTEN_ADDRESS')) -- Cleared on startup. - eq(addr, api.nvim_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[^/\\]*]], api.nvim_get_vvar('servername')) - end) -end) diff --git a/test/testutil.lua b/test/testutil.lua @@ -16,7 +16,7 @@ local function shell_quote(str) return str end ---- This module uses functions from the context of the test runner. +--- Functions executing in the context of the test runner (not the current nvim test session). --- @class test.testutil local M = { paths = Paths,