Skip to content

Commit 9fab7f8

Browse files
committed
Merge remote-tracking branch 'vim/master'
2 parents 4f63131 + b3e2f00 commit 9fab7f8

File tree

6 files changed

+208
-66
lines changed

6 files changed

+208
-66
lines changed

Filelist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ SRC_ALL = \
9191
src/testdir/README.txt \
9292
src/testdir/Make_all.mak \
9393
src/testdir/*.in \
94+
src/testdir/*.py \
9495
src/testdir/sautest/autoload/*.vim \
9596
src/testdir/runtest.vim \
9697
src/testdir/test[0-9]*.ok \

src/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,6 +2029,7 @@ test_arglist \
20292029
test_assert \
20302030
test_backspace_opt \
20312031
test_cdo \
2032+
test_channel \
20322033
test_cursor_func \
20332034
test_delete \
20342035
test_expand \
@@ -2569,6 +2570,7 @@ shadow: runtime pixmaps
25692570
../../testdir/Make_all.mak \
25702571
../../testdir/*.in \
25712572
../../testdir/*.vim \
2573+
../../testdir/*.py \
25722574
../../testdir/python* \
25732575
../../testdir/sautest \
25742576
../../testdir/test83-tags? \

src/channel.c

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -136,22 +136,25 @@ FILE *debugfd = NULL;
136136
add_channel(void)
137137
{
138138
int idx;
139-
channel_T *new_channels;
140139
channel_T *ch;
141140

142141
if (channels != NULL)
142+
{
143143
for (idx = 0; idx < channel_count; ++idx)
144144
if (channels[idx].ch_fd < 0)
145145
/* re-use a closed channel slot */
146146
return idx;
147-
if (channel_count == MAX_OPEN_CHANNELS)
148-
return -1;
149-
new_channels = (channel_T *)alloc(sizeof(channel_T) * (channel_count + 1));
150-
if (new_channels == NULL)
151-
return -1;
152-
if (channels != NULL)
153-
mch_memmove(new_channels, channels, sizeof(channel_T) * channel_count);
154-
channels = new_channels;
147+
if (channel_count == MAX_OPEN_CHANNELS)
148+
return -1;
149+
}
150+
else
151+
{
152+
channels = (channel_T *)alloc((int)sizeof(channel_T)
153+
* MAX_OPEN_CHANNELS);
154+
if (channels == NULL)
155+
return -1;
156+
}
157+
155158
ch = &channels[channel_count];
156159
(void)vim_memset(ch, 0, sizeof(channel_T));
157160

@@ -716,17 +719,21 @@ channel_exe_cmd(int idx, char_u *cmd, typval_T *arg2, typval_T *arg3)
716719
{
717720
int is_eval = cmd[1] == 'v';
718721

719-
if (is_eval && arg3->v_type != VAR_NUMBER)
722+
if (is_eval && (arg3 == NULL || arg3->v_type != VAR_NUMBER))
720723
{
721724
if (p_verbose > 2)
722725
EMSG("E904: third argument for eval must be a number");
723726
}
724727
else
725728
{
726-
typval_T *tv = eval_expr(arg, NULL);
729+
typval_T *tv;
727730
typval_T err_tv;
728731
char_u *json;
729732

733+
/* Don't pollute the display with errors. */
734+
++emsg_skip;
735+
tv = eval_expr(arg, NULL);
736+
--emsg_skip;
730737
if (is_eval)
731738
{
732739
if (tv == NULL)
@@ -739,7 +746,8 @@ channel_exe_cmd(int idx, char_u *cmd, typval_T *arg2, typval_T *arg3)
739746
channel_send(idx, json, "eval");
740747
vim_free(json);
741748
}
742-
free_tv(tv);
749+
if (tv != &err_tv)
750+
free_tv(tv);
743751
}
744752
}
745753
else if (p_verbose > 2)
@@ -791,7 +799,7 @@ may_invoke_callback(int idx)
791799
typval_T *arg3 = NULL;
792800
char_u *cmd = typetv->vval.v_string;
793801

794-
/* ["cmd", arg] */
802+
/* ["cmd", arg] or ["cmd", arg, arg] */
795803
if (list->lv_len == 3)
796804
arg3 = &list->lv_last->li_tv;
797805
channel_exe_cmd(idx, cmd, &argv[1], arg3);
@@ -1144,7 +1152,8 @@ channel_read_json_block(int ch_idx, int id, typval_T **rettv)
11441152

11451153
/* Wait for up to 2 seconds.
11461154
* TODO: use timeout set on the channel. */
1147-
if (channel_wait(channels[ch_idx].ch_fd, 2000) == FAIL)
1155+
if (channels[ch_idx].ch_fd < 0
1156+
|| channel_wait(channels[ch_idx].ch_fd, 2000) == FAIL)
11481157
break;
11491158
channel_read(ch_idx);
11501159
}

src/testdir/test_channel.py

100755100644
Lines changed: 75 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,6 @@
77
# Then Vim can send requests to the server:
88
# :let response = ch_sendexpr(handle, 'hello!')
99
#
10-
# And you can control Vim by typing a JSON message here, e.g.:
11-
# ["ex","echo 'hi there'"]
12-
#
13-
# There is no prompt, just type a line and press Enter.
14-
# To exit cleanly type "quit<Enter>".
15-
#
1610
# See ":help channel-demo" in Vim.
1711
#
1812
# This requires Python 2.6 or later.
@@ -30,58 +24,95 @@
3024
# Python 2
3125
import SocketServer as socketserver
3226

33-
thesocket = None
34-
3527
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
3628

3729
def handle(self):
3830
print("=== socket opened ===")
39-
global thesocket
40-
thesocket = self.request
4131
while True:
4232
try:
43-
data = self.request.recv(4096).decode('utf-8')
33+
received = self.request.recv(4096).decode('utf-8')
4434
except socket.error:
4535
print("=== socket error ===")
4636
break
4737
except IOError:
4838
print("=== socket closed ===")
4939
break
50-
if data == '':
40+
if received == '':
5141
print("=== socket closed ===")
5242
break
53-
print("received: {}".format(data))
54-
try:
55-
decoded = json.loads(data)
56-
except ValueError:
57-
print("json decoding failed")
58-
decoded = [-1, '']
59-
60-
# Send a response if the sequence number is positive.
61-
# Negative numbers are used for "eval" responses.
62-
if decoded[0] >= 0:
63-
if decoded[1] == 'hello!':
64-
# simply send back a string
65-
response = "got it"
66-
elif decoded[1] == 'make change':
67-
# Send two ex commands at the same time, before replying to
68-
# the request.
69-
cmd = '["ex","call append(\\"$\\",\\"added1\\")"]'
70-
cmd += '["ex","call append(\\"$\\",\\"added2\\")"]'
71-
print("sending: {}".format(cmd))
72-
thesocket.sendall(cmd.encode('utf-8'))
73-
response = "ok"
74-
elif decoded[1] == '!quit!':
75-
# we're done
76-
sys.exit(0)
43+
print("received: {}".format(received))
44+
45+
# We may receive two messages at once. Take the part up to the
46+
# matching "]" (recognized by finding "][").
47+
todo = received
48+
while todo != '':
49+
splitidx = todo.find('][')
50+
if splitidx < 0:
51+
used = todo
52+
todo = ''
7753
else:
78-
response = "what?"
54+
used = todo[:splitidx + 1]
55+
todo = todo[splitidx + 1:]
56+
if used != received:
57+
print("using: {}".format(used))
58+
59+
try:
60+
decoded = json.loads(used)
61+
except ValueError:
62+
print("json decoding failed")
63+
decoded = [-1, '']
7964

80-
encoded = json.dumps([decoded[0], response])
81-
print("sending: {}".format(encoded))
82-
thesocket.sendall(encoded.encode('utf-8'))
65+
# Send a response if the sequence number is positive.
66+
if decoded[0] >= 0:
67+
if decoded[1] == 'hello!':
68+
# simply send back a string
69+
response = "got it"
70+
elif decoded[1] == 'make change':
71+
# Send two ex commands at the same time, before
72+
# replying to the request.
73+
cmd = '["ex","call append(\\"$\\",\\"added1\\")"]'
74+
cmd += '["ex","call append(\\"$\\",\\"added2\\")"]'
75+
print("sending: {}".format(cmd))
76+
self.request.sendall(cmd.encode('utf-8'))
77+
response = "ok"
78+
elif decoded[1] == 'eval-works':
79+
# Send an eval request. We ignore the response.
80+
cmd = '["eval","\\"foo\\" . 123", -1]'
81+
print("sending: {}".format(cmd))
82+
self.request.sendall(cmd.encode('utf-8'))
83+
response = "ok"
84+
elif decoded[1] == 'eval-fails':
85+
# Send an eval request that will fail.
86+
cmd = '["eval","xxx", -2]'
87+
print("sending: {}".format(cmd))
88+
self.request.sendall(cmd.encode('utf-8'))
89+
response = "ok"
90+
elif decoded[1] == 'eval-bad':
91+
# Send an eval request missing the third argument.
92+
cmd = '["eval","xxx"]'
93+
print("sending: {}".format(cmd))
94+
self.request.sendall(cmd.encode('utf-8'))
95+
response = "ok"
96+
elif decoded[1] == 'eval-result':
97+
# Send back the last received eval result.
98+
response = last_eval
99+
elif decoded[1] == '!quit!':
100+
# we're done
101+
self.server.shutdown()
102+
break
103+
elif decoded[1] == '!crash!':
104+
# Crash!
105+
42 / 0
106+
else:
107+
response = "what?"
83108

84-
thesocket = None
109+
encoded = json.dumps([decoded[0], response])
110+
print("sending: {}".format(encoded))
111+
self.request.sendall(encoded.encode('utf-8'))
112+
113+
# Negative numbers are used for "eval" responses.
114+
elif decoded[0] < 0:
115+
last_eval = decoded
85116

86117
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
87118
pass
@@ -97,14 +128,14 @@ class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
97128
server_thread = threading.Thread(target=server.serve_forever)
98129

99130
# Exit the server thread when the main thread terminates
100-
server_thread.daemon = True
101131
server_thread.start()
102132

103133
# Write the port number in Xportnr, so that the test knows it.
104134
f = open("Xportnr", "w")
105135
f.write("{}".format(port))
106136
f.close()
107137

108-
# Block here
109138
print("Listening on port {}".format(port))
110-
server.serve_forever()
139+
140+
# Main thread terminates, but the server continues running
141+
# until server.shutdown() is called.

src/testdir/test_channel.vim

Lines changed: 87 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,32 @@
22
scriptencoding utf-8
33

44
" This requires the Python command to run the test server.
5-
" This most likely only works on Unix.
6-
if !has('unix') || !executable('python')
5+
" This most likely only works on Unix and Windows console.
6+
if has('unix')
7+
" We also need the pkill command to make sure the server can be stopped.
8+
if !executable('python') || !executable('pkill')
9+
finish
10+
endif
11+
elseif has('win32') && !has('gui_win32')
12+
" Use Python Launcher for Windows (py.exe).
13+
if !executable('py')
14+
finish
15+
endif
16+
else
717
finish
818
endif
919

10-
func Test_communicate()
20+
let s:port = -1
21+
22+
func s:start_server()
1123
" The Python program writes the port number in Xportnr.
12-
silent !./test_channel.py&
24+
call delete("Xportnr")
25+
26+
if has('win32')
27+
silent !start cmd /c start "test_channel" py test_channel.py
28+
else
29+
silent !python test_channel.py&
30+
endif
1331

1432
" Wait for up to 2 seconds for the port number to be there.
1533
let cnt = 20
@@ -29,11 +47,29 @@ func Test_communicate()
2947

3048
if len(l) == 0
3149
" Can't make the connection, give up.
32-
call system("killall test_channel.py")
50+
call s:kill_server()
51+
call assert_false(1, "Can't start test_channel.py")
52+
return -1
53+
endif
54+
let s:port = l[0]
55+
56+
let handle = ch_open('localhost:' . s:port, 'json')
57+
return handle
58+
endfunc
59+
60+
func s:kill_server()
61+
if has('win32')
62+
call system('taskkill /IM py.exe /T /F /FI "WINDOWTITLE eq test_channel"')
63+
else
64+
call system("pkill -f test_channel.py")
65+
endif
66+
endfunc
67+
68+
func Test_communicate()
69+
let handle = s:start_server()
70+
if handle < 0
3371
return
3472
endif
35-
let port = l[0]
36-
let handle = ch_open('localhost:' . port, 'json')
3773

3874
" Simple string request and reply.
3975
call assert_equal('got it', ch_sendexpr(handle, 'hello!'))
@@ -46,8 +82,51 @@ func Test_communicate()
4682
call assert_equal('added1', getline(line('$') - 1))
4783
call assert_equal('added2', getline('$'))
4884

85+
" Send an eval request that works.
86+
call assert_equal('ok', ch_sendexpr(handle, 'eval-works'))
87+
call assert_equal([-1, 'foo123'], ch_sendexpr(handle, 'eval-result'))
88+
89+
" Send an eval request that fails.
90+
call assert_equal('ok', ch_sendexpr(handle, 'eval-fails'))
91+
call assert_equal([-2, 'ERROR'], ch_sendexpr(handle, 'eval-result'))
92+
93+
" Send a bad eval request. There will be no response.
94+
call assert_equal('ok', ch_sendexpr(handle, 'eval-bad'))
95+
call assert_equal([-2, 'ERROR'], ch_sendexpr(handle, 'eval-result'))
96+
4997
" make the server quit, can't check if this works, should not hang.
5098
call ch_sendexpr(handle, '!quit!', 0)
5199

52-
call system("killall test_channel.py")
100+
call s:kill_server()
101+
endfunc
102+
103+
" Test that we can open two channels.
104+
func Test_two_channels()
105+
let handle = s:start_server()
106+
if handle < 0
107+
return
108+
endif
109+
call assert_equal('got it', ch_sendexpr(handle, 'hello!'))
110+
111+
let newhandle = ch_open('localhost:' . s:port, 'json')
112+
call assert_equal('got it', ch_sendexpr(newhandle, 'hello!'))
113+
call assert_equal('got it', ch_sendexpr(handle, 'hello!'))
114+
115+
call ch_close(handle)
116+
call assert_equal('got it', ch_sendexpr(newhandle, 'hello!'))
117+
118+
call s:kill_server()
119+
endfunc
120+
121+
" Test that a server crash is handled gracefully.
122+
func Test_server_crash()
123+
let handle = s:start_server()
124+
if handle < 0
125+
return
126+
endif
127+
call ch_sendexpr(handle, '!crash!')
128+
129+
" kill the server in case if failed to crash
130+
sleep 10m
131+
call s:kill_server()
53132
endfunc

0 commit comments

Comments
 (0)