patch 8.2.4684: cannot open a channel on a Unix domain socket
Problem: Cannot open a channel on a Unix domain socket.
Solution: Add Unix domain socket support. (closes #10062)
diff --git a/src/testdir/check.vim b/src/testdir/check.vim
index 55a64bd..aff0918 100644
--- a/src/testdir/check.vim
+++ b/src/testdir/check.vim
@@ -95,7 +95,7 @@
endif
endfunc
-" Command to check for running on Linix
+" Command to check for running on Linux
command CheckLinux call CheckLinux()
func CheckLinux()
if !has('linux')
diff --git a/src/testdir/shared.vim b/src/testdir/shared.vim
index 01d741c..8e3dfc9 100644
--- a/src/testdir/shared.vim
+++ b/src/testdir/shared.vim
@@ -15,10 +15,16 @@
if has('unix')
" We also need the job feature or the pkill command to make sure the server
" can be stopped.
- if !(executable('python') && (has('job') || executable('pkill')))
+ if !(has('job') || executable('pkill'))
return ''
endif
- let s:python = 'python'
+ if executable('python')
+ let s:python = 'python'
+ elseif executable('python3')
+ let s:python = 'python3'
+ else
+ return ''
+ end
elseif has('win32')
" Use Python Launcher for Windows (py.exe) if available.
" NOTE: if you get a "Python was not found" error, disable the Python
diff --git a/src/testdir/test_channel.py b/src/testdir/test_channel.py
index 36aad2b..b0c3140 100644
--- a/src/testdir/test_channel.py
+++ b/src/testdir/test_channel.py
@@ -22,7 +22,8 @@
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def setup(self):
- self.request.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ if self.server.address_family != socket.AF_UNIX:
+ self.request.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
def handle(self):
print("=== socket opened ===")
diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim
index 122bc57..6ad9dc0 100644
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -23,6 +23,9 @@
if g:testfunc =~ '_ipv6()$'
let s:localhost = '[::1]:'
let s:testscript = 'test_channel_6.py'
+ elseif g:testfunc =~ '_unix()$'
+ let s:localhost = 'unix:Xtestsocket'
+ let s:testscript = 'test_channel_unix.py'
else
let s:localhost = 'localhost:'
let s:testscript = 'test_channel.py'
@@ -39,6 +42,15 @@
call RunServer(s:testscript, a:testfunc, a:000)
endfunc
+" Returns the address of the test server.
+func s:address(port)
+ if s:localhost =~ '^unix:'
+ return s:localhost
+ else
+ return s:localhost . a:port
+ end
+endfunc
+
" Return a list of open files.
" Can be used to make sure no resources leaked.
" Returns an empty list on systems where this is not supported.
@@ -65,7 +77,7 @@
let s:chopt.drop = 'never'
" Also add the noblock flag to try it out.
let s:chopt.noblock = 1
- let handle = ch_open(s:localhost . a:port, s:chopt)
+ let handle = ch_open(s:address(a:port), s:chopt)
if ch_status(handle) == "fail"
call assert_report("Can't open channel")
return
@@ -77,7 +89,10 @@
let dict = handle->ch_info()
call assert_true(dict.id != 0)
call assert_equal('open', dict.status)
- call assert_equal(a:port, string(dict.port))
+ if has_key(dict, 'port')
+ " Channels using Unix sockets have no 'port' entry.
+ call assert_equal(a:port, string(dict.port))
+ end
call assert_equal('open', dict.sock_status)
call assert_equal('socket', dict.sock_io)
@@ -252,13 +267,19 @@
func Test_communicate_ipv6()
CheckIPv6
-
call Test_communicate()
endfunc
+func Test_communicate_unix()
+ CheckUnix
+ call Test_communicate()
+ call delete('Xtestsocket')
+endfunc
+
+
" Test that we can open two channels.
func Ch_two_channels(port)
- let handle = ch_open(s:localhost . a:port, s:chopt)
+ let handle = ch_open(s:address(a:port), s:chopt)
call assert_equal(v:t_channel, type(handle))
if handle->ch_status() == "fail"
call assert_report("Can't open channel")
@@ -267,7 +288,7 @@
call assert_equal('got it', ch_evalexpr(handle, 'hello!'))
- let newhandle = ch_open(s:localhost . a:port, s:chopt)
+ let newhandle = ch_open(s:address(a:port), s:chopt)
if ch_status(newhandle) == "fail"
call assert_report("Can't open second channel")
return
@@ -292,9 +313,15 @@
call Test_two_channels()
endfunc
+func Test_two_channels_unix()
+ CheckUnix
+ call Test_two_channels()
+ call delete('Xtestsocket')
+endfunc
+
" Test that a server crash is handled gracefully.
func Ch_server_crash(port)
- let handle = ch_open(s:localhost . a:port, s:chopt)
+ let handle = ch_open(s:address(a:port), s:chopt)
if ch_status(handle) == "fail"
call assert_report("Can't open channel")
return
@@ -314,6 +341,12 @@
call Test_server_crash()
endfunc
+func Test_server_crash_unix()
+ CheckUnix
+ call Test_server_crash()
+ call delete('Xtestsocket')
+endfunc
+
"""""""""
func Ch_handler(chan, msg)
@@ -323,7 +356,7 @@
endfunc
func Ch_channel_handler(port)
- let handle = ch_open(s:localhost . a:port, s:chopt)
+ let handle = ch_open(s:address(a:port), s:chopt)
if ch_status(handle) == "fail"
call assert_report("Can't open channel")
return
@@ -352,6 +385,12 @@
call Test_channel_handler()
endfunc
+func Test_channel_handler_unix()
+ CheckUnix
+ call Test_channel_handler()
+ call delete('Xtestsocket')
+endfunc
+
"""""""""
let g:Ch_reply = ''
@@ -367,7 +406,7 @@
endfunc
func Ch_channel_zero(port)
- let handle = (s:localhost .. a:port)->ch_open(s:chopt)
+ let handle = (s:address(a:port))->ch_open(s:chopt)
if ch_status(handle) == "fail"
call assert_report("Can't open channel")
return
@@ -415,6 +454,13 @@
call Test_zero_reply()
endfunc
+func Test_zero_reply_unix()
+ CheckUnix
+ call Test_zero_reply()
+ call delete('Xtestsocket')
+endfunc
+
+
"""""""""
let g:Ch_reply1 = ""
@@ -436,7 +482,7 @@
endfunc
func Ch_raw_one_time_callback(port)
- let handle = ch_open(s:localhost . a:port, s:chopt)
+ let handle = ch_open(s:address(a:port), s:chopt)
if ch_status(handle) == "fail"
call assert_report("Can't open channel")
return
@@ -462,6 +508,12 @@
call Test_raw_one_time_callback()
endfunc
+func Test_raw_one_time_callback_unix()
+ CheckUnix
+ call Test_raw_one_time_callback()
+ call delete('Xtestsocket')
+endfunc
+
"""""""""
" Test that trying to connect to a non-existing port fails quickly.
@@ -1398,7 +1450,7 @@
" Test that "unlet handle" in a handler doesn't crash Vim.
func Ch_unlet_handle(port)
- let s:channelfd = ch_open(s:localhost . a:port, s:chopt)
+ let s:channelfd = ch_open(s:address(a:port), s:chopt)
eval s:channelfd->ch_sendexpr("test", {'callback': function('s:UnletHandler')})
call WaitForAssert({-> assert_equal('what?', g:Ch_unletResponse)})
endfunc
@@ -1422,7 +1474,7 @@
" Test that "unlet handle" in a handler doesn't crash Vim.
func Ch_close_handle(port)
- let s:channelfd = ch_open(s:localhost . a:port, s:chopt)
+ let s:channelfd = ch_open(s:address(a:port), s:chopt)
call ch_sendexpr(s:channelfd, "test", {'callback': function('Ch_CloseHandler')})
call WaitForAssert({-> assert_equal('what?', g:Ch_unletResponse)})
endfunc
@@ -1439,7 +1491,7 @@
""""""""""
func Ch_open_ipv6(port)
- let handle = ch_open('[::1]:' .. a:port, s:chopt)
+ let handle = ch_open(s:address(a:port), s:chopt)
call assert_notequal('fail', ch_status(handle))
endfunc
@@ -1479,7 +1531,7 @@
func Ch_open_delay(port)
" Wait up to a second for the port to open.
let s:chopt.waittime = 1000
- let channel = ch_open(s:localhost . a:port, s:chopt)
+ let channel = ch_open(s:address(a:port), s:chopt)
if ch_status(channel) == "fail"
call assert_report("Can't open channel")
return
@@ -1505,7 +1557,7 @@
endfunc
function Ch_test_call(port)
- let handle = ch_open(s:localhost . a:port, s:chopt)
+ let handle = ch_open(s:address(a:port), s:chopt)
if ch_status(handle) == "fail"
call assert_report("Can't open channel")
return
@@ -1529,6 +1581,12 @@
call Test_call()
endfunc
+func Test_call_unix()
+ CheckUnix
+ call Test_call()
+ call delete('Xtestsocket')
+endfunc
+
"""""""""
let g:Ch_job_exit_ret = 'not yet'
@@ -1605,7 +1663,7 @@
endfunc
function Ch_test_close_callback(port)
- let handle = ch_open(s:localhost . a:port, s:chopt)
+ let handle = ch_open(s:address(a:port), s:chopt)
if ch_status(handle) == "fail"
call assert_report("Can't open channel")
return
@@ -1625,8 +1683,14 @@
call Test_close_callback()
endfunc
+func Test_close_callback_unix()
+ CheckUnix
+ call Test_close_callback()
+ call delete('Xtestsocket')
+endfunc
+
function Ch_test_close_partial(port)
- let handle = ch_open(s:localhost . a:port, s:chopt)
+ let handle = ch_open(s:address(a:port), s:chopt)
if ch_status(handle) == "fail"
call assert_report("Can't open channel")
return
@@ -1651,6 +1715,12 @@
call Test_close_partial()
endfunc
+func Test_close_partial_unix()
+ CheckUnix
+ call Test_close_partial()
+ call delete('Xtestsocket')
+endfunc
+
func Test_job_start_fails()
" this was leaking memory
call assert_fails("call job_start([''])", "E474:")
@@ -1920,7 +1990,7 @@
endfunc
function Ch_test_close_lambda(port)
- let handle = ch_open(s:localhost . a:port, s:chopt)
+ let handle = ch_open(s:address(a:port), s:chopt)
if ch_status(handle) == "fail"
call assert_report("Can't open channel")
return
@@ -1942,6 +2012,12 @@
call Test_close_lambda()
endfunc
+func Test_close_lambda_unix()
+ CheckUnix
+ call Test_close_lambda()
+ call delete('Xtestsocket')
+endfunc
+
func s:test_list_args(cmd, out, remove_lf)
try
let g:out = ''
@@ -2243,6 +2319,8 @@
let job = job_start("cat ", #{in_io: 'null'})
call WaitForAssert({-> assert_equal("dead", job_status(job))})
call assert_equal(0, job_info(job).exitval)
+
+ call delete('Xtestsocket')
endfunc
func Test_ch_getbufnr()
diff --git a/src/testdir/test_channel_unix.py b/src/testdir/test_channel_unix.py
new file mode 100644
index 0000000..4836e26
--- /dev/null
+++ b/src/testdir/test_channel_unix.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+#
+# Server that will accept connections from a Vim channel.
+# Used by test_channel.vim.
+#
+# This requires Python 2.6 or later.
+
+from __future__ import print_function
+from test_channel import ThreadedTCPServer, ThreadedTCPRequestHandler, \
+ writePortInFile
+import socket
+import threading
+import os
+
+try:
+ FileNotFoundError
+except NameError:
+ # Python 2
+ FileNotFoundError = (IOError, OSError)
+
+class ThreadedUnixServer(ThreadedTCPServer):
+ address_family = socket.AF_UNIX
+
+def main(path):
+ server = ThreadedUnixServer(path, ThreadedTCPRequestHandler)
+
+ # Start a thread with the server. That thread will then start a new thread
+ # for each connection.
+ server_thread = threading.Thread(target=server.serve_forever)
+ server_thread.start()
+
+ # Signal the test harness we're ready, the port value has no meaning.
+ writePortInFile(1234)
+
+ print("Listening on {0}".format(server.server_address))
+
+ # Main thread terminates, but the server continues running
+ # until server.shutdown() is called.
+ try:
+ while server_thread.is_alive():
+ server_thread.join(1)
+ except (KeyboardInterrupt, SystemExit):
+ server.shutdown()
+
+if __name__ == "__main__":
+ try:
+ os.remove("Xtestsocket")
+ except FileNotFoundError:
+ pass
+ main("Xtestsocket")
diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
index e38a569..8034bdb 100644
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -620,8 +620,8 @@
\ ':5s': 'substitute',
\ "'<,'>s": 'substitute',
\ ":'<,'>s": 'substitute',
- \ 'CheckUni': 'CheckUnix',
- \ 'CheckUnix': 'CheckUnix',
+ \ 'CheckLin': 'CheckLinux',
+ \ 'CheckLinux': 'CheckLinux',
\ }
for [in, want] in items(tests)