mirror of
https://github.com/neovim/neovim.git
synced 2026-11-06 05:16:52 +00:00
Problem: - remote_ssh.sh detects ssh disconnect by writing a heartbeat every 1s, which the local side buffers for the whole session. - The server's stdout (e.g. `io.write()` from a random plugin) writes to the tunnel, which in theory may contain a bogus "NVIM_READY". - A nightly install is never updated on the remote. Solution: - Supervise the server in `vim.net._ssh.serve()` (Lua). It detects disconnect via stdin EOF and discards server stdout. The script only finds or installs `nvim`. - Also a step toward Windows remotes. - User may choose "Update Nvim" to reinstall the nightly.
513 lines
19 KiB
Lua
513 lines
19 KiB
Lua
local t = require('test.testutil')
|
|
local n = require('test.functional.testnvim')()
|
|
local Screen = require('test.functional.ui.screen')
|
|
local parser = require('vim.net._ssh')
|
|
local describe, it = t.describe, t.it
|
|
local before_each = t.before_each
|
|
local clear = n.clear
|
|
local eq = t.eq
|
|
local is_os = t.is_os
|
|
local skip = t.skip
|
|
|
|
describe('parse_ssh_config()', function()
|
|
it('parses SSH configuration strings', function()
|
|
local config = [[
|
|
Host *
|
|
ConnectTimeout 10
|
|
ServerAliveInterval 60
|
|
ServerAliveCountMax 3
|
|
# Use a specific key for any host not otherwise specified
|
|
# IdentityFile ~/.ssh/id_rsa
|
|
|
|
Host=dev
|
|
HostName=dev.example.com
|
|
User=devuser
|
|
Port=2222
|
|
IdentityFile=~/.ssh/id_rsa_dev
|
|
|
|
Host prod test
|
|
HostName 198.51.100.10
|
|
User admin
|
|
Port 22
|
|
IdentityFile ~/.ssh/id_rsa_prod
|
|
ForwardAgent yes
|
|
|
|
Host test
|
|
IdentitiesOnly yes
|
|
|
|
Host "quoted string"
|
|
User quote
|
|
Port 22
|
|
|
|
Match host foo host gh
|
|
HostName github.com
|
|
User git
|
|
IdentityFile ~/.ssh/id_rsa_github
|
|
IdentitiesOnly yes
|
|
]]
|
|
|
|
local hosts = parser.parse_ssh_config(config)
|
|
eq(
|
|
{ 'dev', 'prod', 'test', 'quoted string', 'foo', 'gh' },
|
|
vim.tbl_map(function(h)
|
|
return h.alias
|
|
end, hosts)
|
|
)
|
|
eq('dev.example.com', hosts[1].hostname)
|
|
eq('devuser', hosts[1].user)
|
|
eq('2222', hosts[1].port)
|
|
eq('198.51.100.10', hosts[2].hostname)
|
|
eq('admin', hosts[2].user)
|
|
end)
|
|
|
|
it('ignores repeated and trailing separators', function()
|
|
-- Runs of separators between values, and trailing whitespace before the line break, must not
|
|
-- yield empty aliases.
|
|
local config = table.concat({
|
|
'Host alpha beta ',
|
|
' HostName example.com',
|
|
'',
|
|
}, '\n')
|
|
|
|
eq(
|
|
{ 'alpha', 'beta' },
|
|
vim.tbl_map(function(h)
|
|
return h.alias
|
|
end, parser.parse_ssh_config(config))
|
|
)
|
|
end)
|
|
|
|
it('fails when a quote is not closed', function()
|
|
local config = [[
|
|
Host prod dev "test prod my
|
|
HostName 198.51.100.10
|
|
User admin
|
|
Port 22
|
|
IdentityFile ~/.ssh/id_rsa_prod
|
|
ForwardAgent yes
|
|
]]
|
|
|
|
local ok, _ = pcall(parser.parse_ssh_config, config)
|
|
eq(false, ok)
|
|
end)
|
|
|
|
it('fails when the line ends with a single backslash', function()
|
|
local config = [[
|
|
Host prod test
|
|
HostName 198.51.100.10
|
|
User admin\
|
|
Port 22
|
|
IdentityFile ~/.ssh/id_rsa_prod
|
|
ForwardAgent yes
|
|
]]
|
|
|
|
local ok, _ = pcall(parser.parse_ssh_config, config)
|
|
eq(false, ok)
|
|
end)
|
|
end)
|
|
|
|
describe('remote-ssh', function()
|
|
local fake_dir --- @type string
|
|
|
|
--- Makes `vim.net._ssh` run a fake `ssh` (fixtures/fake-ssh.lua), and stubs `confirm()`.
|
|
---
|
|
--- @param behavior? table Optional fields:
|
|
--- - `confirm`: Result of `confirm()`.
|
|
--- - `ssh_config`, `uname`: Output of `ssh -G`, of `uname -s && uname -m`.
|
|
--- - `tunnel`: The tunnel (`-L`) command, by default `{ serve = true }`:
|
|
--- - `serve`: Serves a headless Nvim at the `-L` socket, then prints NVIM_READY.
|
|
--- - `stdout`, `stderr`: Printed.
|
|
--- - `exit`: Exit code. Else runs until the served Nvim exits, or until killed.
|
|
local function setup_fake_ssh(behavior)
|
|
behavior = behavior or {}
|
|
local dir = vim.fs.abspath(t.tmpname(false))
|
|
fake_dir = dir
|
|
n.mkdir_p(dir)
|
|
t.finally(function()
|
|
n.rmdir(dir)
|
|
end)
|
|
local nvim_prog = vim.fs.abspath(n.nvim_prog)
|
|
local config = vim.json.encode({
|
|
dir = fake_dir,
|
|
nvim_prog = nvim_prog,
|
|
ssh_config = behavior.ssh_config or 'stricthostkeychecking ask\nserveraliveinterval 0\n',
|
|
uname = behavior.uname or 'Linux\nx86_64\n',
|
|
tunnel = behavior.tunnel or { serve = true },
|
|
})
|
|
local fake_ssh = vim.fs.abspath('test/functional/fixtures/fake-ssh.lua')
|
|
|
|
n.exec_lua(function(ssh_cmd, choice)
|
|
require('vim.net._ssh').ssh_cmd = ssh_cmd
|
|
_G.confirm_log = {}
|
|
vim.fn.confirm = function(...)
|
|
table.insert(_G.confirm_log, { ... })
|
|
return choice
|
|
end
|
|
end, { nvim_prog, '-l', fake_ssh, config }, behavior.confirm or 1)
|
|
end
|
|
|
|
--- Checks that the fake tunnel process has exited.
|
|
local function tunnel_exited()
|
|
local pid = assert(tonumber(t.read_file(('%s/tunnel.pid'):format(fake_dir))))
|
|
return vim.uv.kill(pid, 0) ~= 0
|
|
end
|
|
|
|
before_each(function()
|
|
clear()
|
|
end)
|
|
|
|
it('get_system_info() detects OS and arch', function()
|
|
for _, c in ipairs({
|
|
{ 'Linux\nx86_64\n', { 'linux', 'x86_64' } },
|
|
{ 'Darwin\narm64\n', { 'darwin', 'arm64' } },
|
|
}) do
|
|
setup_fake_ssh({ uname = c[1] })
|
|
eq(
|
|
c[2],
|
|
n.exec_lua(function()
|
|
return { require('vim.net._ssh').get_system_info({ host = 'server' }) }
|
|
end)
|
|
)
|
|
end
|
|
|
|
setup_fake_ssh({ uname = 'MSYS_NT-10.0-19045\nx86_64\n' })
|
|
t.matches(
|
|
'Windows targets are not supported',
|
|
t.pcall_err(n.exec_lua, function()
|
|
require('vim.net._ssh').get_system_info({ host = 'server' })
|
|
end)
|
|
)
|
|
end)
|
|
|
|
it('connect() returns the local socket of the tunnel', function()
|
|
skip(is_os('win'), 'remote-ssh is POSIX-only')
|
|
setup_fake_ssh()
|
|
local sock, logfile, progress = unpack(n.exec_lua([[
|
|
local logfile = vim.fs.joinpath(vim.fn.stdpath('log'), 'remote-ssh.log')
|
|
os.remove(logfile)
|
|
local progress = {}
|
|
vim.api.nvim_create_autocmd('Progress', {
|
|
pattern = 'remote-ssh',
|
|
callback = function(ev)
|
|
table.insert(progress, ('%s: %s'):format(ev.data.status, table.concat(ev.data.text)))
|
|
end,
|
|
})
|
|
local sock, teardown = require('vim.net._ssh').connect('ssh://user@test-server')
|
|
teardown()
|
|
return { sock, logfile, progress }
|
|
]]))
|
|
|
|
assert(sock:match('_remote_nvim%.sock$'))
|
|
eq({
|
|
'running: Connecting to ssh://user@test-server...',
|
|
'running: Starting remote Nvim...',
|
|
'success: Connected to ssh://user@test-server',
|
|
}, progress)
|
|
local msg = 'This will install Nvim on ssh://user@test-server (in ~/.local/share/nvim-remote/), '
|
|
.. 'if missing.\n'
|
|
local choices = n.exec_lua('return vim.version().prerelease')
|
|
and 'Connect? &Yes\n&Update Nvim\n&No'
|
|
or 'Connect? &Yes\n&No'
|
|
eq({ { msg, choices, 1, 'Question' } }, n.exec_lua('return _G.confirm_log'))
|
|
t.matches('Connected to ssh://user@test%-server', t.read_file(logfile))
|
|
local tunnel_args = t.read_file(fake_dir .. '/tunnel.args')
|
|
t.matches('BatchMode=yes', tunnel_args)
|
|
t.matches('RemoteCommand=none', tunnel_args)
|
|
t.matches('StrictHostKeyChecking=accept%-new', tunnel_args)
|
|
t.matches('ServerAliveInterval=15', tunnel_args)
|
|
-- Teardown stops the tunnel and cancels its forward, but not the local server.
|
|
eq(true, tunnel_exited())
|
|
eq(true, vim.uv.fs_stat(fake_dir .. '/forward.cancelled') ~= nil)
|
|
n.poke_eventloop()
|
|
n.assert_alive()
|
|
|
|
-- Explicit ssh_config values are kept. The target is decoded, with an IPv6 host unbracketed.
|
|
setup_fake_ssh({ ssh_config = 'stricthostkeychecking yes\nserveraliveinterval 60\n' })
|
|
n.exec_lua(function()
|
|
local _, teardown = require('vim.net._ssh').connect('ssh://a%40b@[::1]:2222/x')
|
|
teardown()
|
|
end)
|
|
local explicit_args = t.read_file(fake_dir .. '/tunnel.args')
|
|
t.matches(' %-p 2222 %-%- a@b@::1 sh ', explicit_args)
|
|
eq(nil, explicit_args:find('StrictHostKeyChecking', 1, true))
|
|
eq(nil, explicit_args:find('ServerAliveInterval', 1, true))
|
|
eq(
|
|
'No host in SSH URI: ssh:///x',
|
|
t.pcall_err(n.exec_lua, [[require('vim.net._ssh').connect('ssh:///x')]])
|
|
)
|
|
|
|
-- Stops the session if the UI detached meanwhile.
|
|
setup_fake_ssh()
|
|
t.matches(
|
|
'UI detached: channel 999',
|
|
t.pcall_err(n.exec_lua, [[require('vim.net._ssh').connect('ssh://user@test-server', 999)]])
|
|
)
|
|
eq(true, tunnel_exited())
|
|
eq(true, vim.uv.fs_stat(fake_dir .. '/forward.cancelled') ~= nil)
|
|
end)
|
|
|
|
it('stops the local server when the session ends, unless it is in use', function()
|
|
skip(is_os('win'), 'remote-ssh is POSIX-only')
|
|
setup_fake_ssh()
|
|
local logfile = n.exec_lua([[return vim.fs.joinpath(vim.fn.stdpath('log'), 'remote-ssh.log')]])
|
|
os.remove(logfile)
|
|
-- Connects, then ends the session as the remote does: its Nvim quits.
|
|
local session = [[
|
|
local sock = require('vim.net._ssh').connect('ssh://user@test-server')
|
|
vim.rpcnotify(vim.fn.sockconnect('pipe', sock, { rpc = true }), 'nvim_command', 'qall!')
|
|
]]
|
|
|
|
n.command('set modified')
|
|
n.exec_lua(session)
|
|
t.retry(nil, 5000, function()
|
|
t.matches('local server in use, keeping it', t.read_file(logfile))
|
|
end)
|
|
n.assert_alive()
|
|
|
|
n.command('set nomodified')
|
|
n.expect_exit(5000, n.exec_lua, session)
|
|
end)
|
|
|
|
it('passes the matching release to the remote script', function()
|
|
skip(is_os('win'), 'remote-ssh is POSIX-only')
|
|
setup_fake_ssh()
|
|
local base, prerelease = unpack(n.exec_lua(function()
|
|
local _, teardown = require('vim.net._ssh').connect('ssh://user@test-server')
|
|
teardown()
|
|
local v = vim.version()
|
|
return { ('v%d.%d.%d'):format(v.major, v.minor, v.patch), v.prerelease and true or false }
|
|
end))
|
|
local url = 'https://github.com/neovim/neovim/releases/download/%s/nvim-linux-x86_64.tar.gz'
|
|
local dir = '.local/share/nvim-remote/releases/%s-linux-x86_64'
|
|
-- A prerelease installs the nightly, and passes no version, so a system `nvim` is never used.
|
|
local args = prerelease and { dir:format('nightly-' .. base), url:format('nightly') }
|
|
or { dir:format(base), url:format(base), base }
|
|
eq(
|
|
table.concat(args, ' '),
|
|
t.read_file(fake_dir .. '/tunnel.args')
|
|
:match(' sh %-s %-%- /tmp/nvim%-remote%.%x+/nvim%.sock (.-) #!/bin/sh')
|
|
)
|
|
|
|
-- "Update Nvim" (prerelease only) re-installs the nightly.
|
|
if prerelease then
|
|
setup_fake_ssh({ confirm = 2 })
|
|
n.exec_lua(function()
|
|
local _, teardown = require('vim.net._ssh').connect('ssh://user@test-server')
|
|
teardown()
|
|
end)
|
|
t.matches(' sh %-s %-%- %-%-update /tmp/', t.read_file(fake_dir .. '/tunnel.args'))
|
|
end
|
|
end)
|
|
|
|
it('remote script runs acceptable `nvim` from $PATH, else installs the release', function()
|
|
skip(is_os('win'), 'remote-ssh is POSIX-only')
|
|
local home = vim.fs.abspath(t.tmpname(false))
|
|
t.finally(function()
|
|
n.rmdir(home)
|
|
end)
|
|
local nvim_prog = vim.fs.abspath(n.nvim_prog)
|
|
n.mkdir_p(home .. '/bin')
|
|
vim.uv.fs_symlink(nvim_prog, home .. '/bin/nvim')
|
|
-- Release archive: its `nvim` marks the server it runs.
|
|
local release = home .. '/src/nvim-test'
|
|
n.mkdir_p(release .. '/bin')
|
|
t.write_file(
|
|
release .. '/bin/nvim',
|
|
('#!/bin/sh\nexport NVIM_TEST_RELEASE=1\nexec %s "$@"\n'):format(nvim_prog)
|
|
)
|
|
vim.uv.fs_chmod(release .. '/bin/nvim', 493)
|
|
local tarball = home .. '/nvim-test.tar.gz'
|
|
eq(0, vim.system({ 'tar', '-czf', tarball, '-C', home .. '/src', 'nvim-test' }):wait().code)
|
|
local dir = 'releases/v1-test'
|
|
|
|
--- Runs the script locally, as the remote does. Returns `[$NVIM_APPNAME, $NVIM_TEST_RELEASE,
|
|
--- v:startreason]` of its server, whether its socket dir was removed on exit, and its
|
|
--- `Installing …` message, if any.
|
|
local function serve(version, update)
|
|
return n.exec_lua(function(...)
|
|
local sock = ('%s/sock/nvim.sock'):format(home)
|
|
local cmd =
|
|
{ 'env', ('HOME=%s'):format(home), ('PATH=%s/bin:%s'):format(home, vim.env.PATH) }
|
|
vim.list_extend(cmd, { 'sh', 'runtime/scripts/remote_ssh.sh' })
|
|
vim.list_extend(cmd, update and { '--update', sock, ... } or { sock, ... })
|
|
local obj = vim.system(cmd, { stdin = true }) -- Use stdin to signal disconnect.
|
|
obj:wait(10000, function(stdout)
|
|
return stdout:match('NVIM_READY') ~= nil
|
|
end)
|
|
local ch = vim.fn.sockconnect('pipe', sock, { rpc = true })
|
|
local env =
|
|
vim.rpcrequest(ch, 'nvim_eval', '[$NVIM_APPNAME, $NVIM_TEST_RELEASE, v:startreason]')
|
|
vim.fn.chanclose(ch)
|
|
obj:write(nil) -- ssh disconnects (stdin closed).
|
|
local res = obj:wait(5000)
|
|
-- `vim.net._ssh.serve()` removes its socket dir on exit.
|
|
local removed = vim.uv.fs_stat(vim.fs.dirname(sock)) == nil
|
|
return { env, removed, res.stderr:match('Installing %S+') }
|
|
end, dir, ('file://%s'):format(tarball), version)
|
|
end
|
|
|
|
eq({ { 'nvim-remote', '', 'remote-ssh' }, true }, serve('v0.1.0'))
|
|
eq(nil, vim.uv.fs_stat(('%s/%s'):format(home, dir)))
|
|
local installing = ('Installing %s/%s...'):format(home, dir)
|
|
eq({ { 'nvim-remote', '1', 'remote-ssh' }, true, installing }, serve('v999.0.0'))
|
|
-- No version (a prerelease): never uses $PATH; reuses the install.
|
|
eq({ { 'nvim-remote', '1', 'remote-ssh' }, true }, serve())
|
|
-- --update replaces the install, and leaves no temp dirs.
|
|
t.write_file(('%s/%s/stale'):format(home, dir), '')
|
|
eq({ { 'nvim-remote', '1', 'remote-ssh' }, true, installing }, serve(nil, true))
|
|
eq({ 'bin' }, n.fn.readdir(('%s/%s'):format(home, dir)))
|
|
eq({ 'v1-test' }, n.fn.readdir(home .. '/releases'))
|
|
|
|
-- Follows a restarted server, which re-binds the socket. The socket dir is private.
|
|
eq(
|
|
{ tonumber('700', 8), true, true },
|
|
n.exec_lua(function()
|
|
local sock = ('%s/sock/nvim.sock'):format(home)
|
|
local cmd =
|
|
{ 'env', ('HOME=%s'):format(home), ('PATH=%s/bin:%s'):format(home, vim.env.PATH) }
|
|
vim.list_extend(cmd, { 'sh', 'runtime/scripts/remote_ssh.sh', sock, dir, '', 'v0.1.0' })
|
|
local obj = vim.system(cmd, { stdin = true })
|
|
obj:wait(10000, function(stdout)
|
|
return stdout:match('NVIM_READY') ~= nil
|
|
end)
|
|
local mode = bit.band(assert(vim.uv.fs_stat(vim.fs.dirname(sock))).mode, tonumber('777', 8))
|
|
-- As `:restart`: the old server stops listening, a new one listens, the old one exits.
|
|
local old = vim.fn.sockconnect('pipe', sock, { rpc = true })
|
|
vim.rpcrequest(old, 'nvim_call_function', 'serverstop', { sock })
|
|
local count_chans = 'let g:chans = 0 | autocmd ChanOpen * let g:chans += 1'
|
|
local new = vim.system({
|
|
vim.v.progpath,
|
|
'--clean',
|
|
'--headless',
|
|
'--cmd',
|
|
count_chans,
|
|
'--listen',
|
|
sock,
|
|
})
|
|
vim.wait(5000, function()
|
|
return vim.uv.fs_stat(sock) ~= nil
|
|
end)
|
|
local ch = vim.fn.sockconnect('pipe', sock, { rpc = true })
|
|
vim.rpcnotify(old, 'nvim_command', 'qall!')
|
|
-- `vim.net._ssh.serve()` connects to the new server (to get its PID).
|
|
assert(vim.wait(5000, function()
|
|
return vim.rpcrequest(ch, 'nvim_eval', 'g:chans') > 1
|
|
end))
|
|
obj:write(nil) -- ssh disconnects: stops the new server.
|
|
return { mode, new:wait(5000).code ~= 124, obj:wait(5000).code == 0 }
|
|
end)
|
|
)
|
|
|
|
-- Stops the server and removes the socket dir also on a signal.
|
|
eq(
|
|
{ true, true },
|
|
n.exec_lua(function()
|
|
local sock = ('%s/sock/nvim.sock'):format(home)
|
|
local cmd =
|
|
{ 'env', ('HOME=%s'):format(home), ('PATH=%s/bin:%s'):format(home, vim.env.PATH) }
|
|
vim.list_extend(cmd, { 'sh', 'runtime/scripts/remote_ssh.sh', sock, dir, '', 'v0.1.0' })
|
|
local obj = vim.system(cmd, { stdin = true })
|
|
obj:wait(10000, function(stdout)
|
|
return stdout:match('NVIM_READY') ~= nil
|
|
end)
|
|
local ch = vim.fn.sockconnect('pipe', sock, { rpc = true })
|
|
local server = vim.rpcrequest(ch, 'nvim_call_function', 'getpid', {})
|
|
vim.uv.kill(obj.pid, 'sigterm') -- The script `exec`s the supervisor.
|
|
obj:wait(5000)
|
|
return {
|
|
vim.uv.fs_stat(vim.fs.dirname(sock)) == nil,
|
|
(vim.wait(5000, function()
|
|
return vim.uv.kill(server, 0) ~= 0
|
|
end)),
|
|
}
|
|
end)
|
|
)
|
|
|
|
-- The UI could not reach a restarted server through the tunnel.
|
|
n.clear({ env = { __NVIM_STARTREASON = 'remote-ssh' } })
|
|
eq(
|
|
'Vim(restart):E6103: :restart is not supported with remote-ssh',
|
|
t.pcall_err(n.command, 'restart')
|
|
)
|
|
end)
|
|
|
|
it('fails when ssh exits before the remote Nvim is ready', function()
|
|
setup_fake_ssh({
|
|
tunnel = { stderr = 'channel 0: open failed: administratively prohibited\n', exit = 255 },
|
|
})
|
|
n.exec_lua(function()
|
|
vim.api.nvim_create_autocmd('Progress', {
|
|
pattern = 'remote-ssh',
|
|
callback = function(ev)
|
|
_G.last_progress = ('%s: %s'):format(ev.data.status, table.concat(ev.data.text))
|
|
end,
|
|
})
|
|
end)
|
|
t.matches(
|
|
'SSH tunnel exited before the remote Nvim was ready: channel 0: open failed',
|
|
t.pcall_err(n.exec_lua, [[require('vim.net._ssh').connect('ssh://user@test-server')]])
|
|
)
|
|
eq('failed: Failed to connect to ssh://user@test-server', n.exec_lua('return _G.last_progress'))
|
|
eq(true, vim.uv.fs_stat(fake_dir .. '/forward.cancelled') ~= nil)
|
|
end)
|
|
|
|
it('fails when the remote Nvim crashes during startup, or is unreachable', function()
|
|
setup_fake_ssh({
|
|
tunnel = { stdout = 'NVIM_CRASHED\n', exit = 1 },
|
|
})
|
|
t.matches(
|
|
'Remote Nvim crashed during startup',
|
|
t.pcall_err(n.exec_lua, [[require('vim.net._ssh').connect('ssh://user@test-server')]])
|
|
)
|
|
|
|
-- The script runs, but sshd refused the forward.
|
|
setup_fake_ssh({
|
|
tunnel = { stdout = 'NVIM_READY\n' },
|
|
})
|
|
t.matches(
|
|
'Remote Nvim is not reachable through the SSH forward',
|
|
t.pcall_err(n.exec_lua, [[require('vim.net._ssh').connect('ssh://user@test-server')]])
|
|
)
|
|
eq(true, vim.uv.fs_stat(fake_dir .. '/forward.cancelled') ~= nil)
|
|
end)
|
|
|
|
it('serves each session from a new remote directory', function()
|
|
skip(is_os('win'), 'remote-ssh is POSIX-only')
|
|
setup_fake_ssh()
|
|
local function remote_dir()
|
|
n.exec_lua([[
|
|
local _, teardown = require('vim.net._ssh').connect('ssh://user@test-server')
|
|
teardown()
|
|
]])
|
|
local args = t.read_file(fake_dir .. '/tunnel.args')
|
|
return args:match(':(/tmp/nvim%-remote%.%x+)/nvim%.sock ')
|
|
end
|
|
|
|
local dir1, dir2 = remote_dir(), remote_dir()
|
|
assert(dir1 and dir2, 'remote socket path not found')
|
|
assert(dir1 ~= dir2, 'remote socket directory reused across sessions')
|
|
end)
|
|
|
|
it(':connect ssh:// does nothing without a UI, or if the user declines', function()
|
|
setup_fake_ssh({ confirm = 0 })
|
|
eq('Vim(connect):E477: No ! allowed', t.pcall_err(n.command, 'connect! ssh://test-server'))
|
|
eq('Vim(connect):E5768: No UI attached', t.pcall_err(n.command, 'connect ssh://test-server'))
|
|
-- Typed by a client that is not a UI.
|
|
n.command('let v:errmsg = ""')
|
|
local client = n.connect(n.eval('v:servername'))
|
|
client:request('nvim_input', ':connect ssh://test-server<CR>')
|
|
t.retry(nil, nil, function()
|
|
eq('E5768: No UI attached', n.eval('v:errmsg'))
|
|
end)
|
|
client:close()
|
|
eq({}, n.exec_lua('return _G.confirm_log'))
|
|
|
|
Screen.new()
|
|
-- Scheme is case-insensitive.
|
|
n.command('connect SSH://test-server')
|
|
eq(1, #n.exec_lua('return _G.confirm_log'))
|
|
eq(1, #n.api.nvim_list_uis())
|
|
eq(nil, vim.uv.fs_stat(fake_dir .. '/tunnel.args'))
|
|
end)
|
|
end)
|