Skip to content

Commit d7ece10

Browse files
committed
patch 7.4.1246
Problem: The channel functionality isn't tested. Solution: Add a test using a Python test server.
1 parent d087566 commit d7ece10

File tree

6 files changed

+242
-40
lines changed

6 files changed

+242
-40
lines changed

src/channel.c

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -523,19 +523,21 @@ channel_collapse(int idx)
523523
}
524524

525525
/*
526-
* Use the read buffer of channel "ch_idx" and parse JSON messages that are
526+
* Use the read buffer of channel "ch_idx" and parse a JSON messages that is
527527
* complete. The messages are added to the queue.
528+
* Return TRUE if there is more to read.
528529
*/
529-
void
530-
channel_read_json(int ch_idx)
530+
static int
531+
channel_parse_json(int ch_idx)
531532
{
532533
js_read_T reader;
533534
typval_T listtv;
534535
jsonq_T *item;
535536
jsonq_T *head = &channels[ch_idx].ch_json_head;
537+
int ret;
536538

537539
if (channel_peek(ch_idx) == NULL)
538-
return;
540+
return FALSE;
539541

540542
/* TODO: make reader work properly */
541543
/* reader.js_buf = channel_peek(ch_idx); */
@@ -544,36 +546,52 @@ channel_read_json(int ch_idx)
544546
reader.js_fill = NULL;
545547
/* reader.js_fill = channel_fill; */
546548
reader.js_cookie = &ch_idx;
547-
if (json_decode(&reader, &listtv) == OK)
549+
ret = json_decode(&reader, &listtv);
550+
if (ret == OK)
548551
{
549-
item = (jsonq_T *)alloc((unsigned)sizeof(jsonq_T));
550-
if (item == NULL)
552+
if (listtv.v_type != VAR_LIST)
553+
{
554+
/* TODO: give error */
551555
clear_tv(&listtv);
556+
}
552557
else
553558
{
554-
item->value = alloc_tv();
555-
if (item->value == NULL)
556-
{
557-
vim_free(item);
559+
item = (jsonq_T *)alloc((unsigned)sizeof(jsonq_T));
560+
if (item == NULL)
558561
clear_tv(&listtv);
559-
}
560562
else
561563
{
562-
*item->value = listtv;
563-
item->prev = head->prev;
564-
head->prev = item;
565-
item->next = head;
566-
item->prev->next = item;
564+
item->value = alloc_tv();
565+
if (item->value == NULL)
566+
{
567+
vim_free(item);
568+
clear_tv(&listtv);
569+
}
570+
else
571+
{
572+
*item->value = listtv;
573+
item->prev = head->prev;
574+
head->prev = item;
575+
item->next = head;
576+
item->prev->next = item;
577+
}
567578
}
568579
}
569580
}
570581

571582
/* Put the unread part back into the channel.
572583
* TODO: insert in front */
573584
if (reader.js_buf[reader.js_used] != NUL)
585+
{
574586
channel_save(ch_idx, reader.js_buf + reader.js_used,
575587
(int)(reader.js_end - reader.js_buf) - reader.js_used);
588+
ret = TRUE;
589+
}
590+
else
591+
ret = FALSE;
592+
576593
vim_free(reader.js_buf);
594+
return ret;
577595
}
578596

579597
/*
@@ -607,7 +625,8 @@ channel_get_json(int ch_idx, int id, typval_T **rettv)
607625
typval_T *tv = &l->lv_first->li_tv;
608626

609627
if ((id > 0 && tv->v_type == VAR_NUMBER && tv->vval.v_number == id)
610-
|| id <= 0)
628+
|| (id <= 0
629+
&& (tv->v_type != VAR_NUMBER || tv->vval.v_number < 0)))
611630
{
612631
*rettv = item->value;
613632
remove_json_node(item);
@@ -717,23 +736,19 @@ may_invoke_callback(int idx)
717736
int seq_nr = -1;
718737
int json_mode = channels[idx].ch_json_mode;
719738

720-
if (channel_peek(idx) == NULL)
721-
return FALSE;
722739
if (channels[idx].ch_close_cb != NULL)
723740
/* this channel is handled elsewhere (netbeans) */
724741
return FALSE;
725742

726743
if (json_mode)
727744
{
728-
/* Get any json message. Return if there isn't one. */
729-
channel_read_json(idx);
745+
/* Get any json message in the queue. */
730746
if (channel_get_json(idx, -1, &listtv) == FAIL)
731-
return FALSE;
732-
if (listtv->v_type != VAR_LIST)
733747
{
734-
/* TODO: give error */
735-
clear_tv(listtv);
736-
return FALSE;
748+
/* Parse readahead, return when there is still no message. */
749+
channel_parse_json(idx);
750+
if (channel_get_json(idx, -1, &listtv) == FAIL)
751+
return FALSE;
737752
}
738753

739754
list = listtv->vval.v_list;
@@ -767,6 +782,11 @@ may_invoke_callback(int idx)
767782
}
768783
seq_nr = typetv->vval.v_number;
769784
}
785+
else if (channel_peek(idx) == NULL)
786+
{
787+
/* nothing to read on raw channel */
788+
return FALSE;
789+
}
770790
else
771791
{
772792
/* For a raw channel we don't know where the message ends, just get
@@ -1080,19 +1100,29 @@ channel_read_block(int idx)
10801100
int
10811101
channel_read_json_block(int ch_idx, int id, typval_T **rettv)
10821102
{
1103+
int more;
1104+
10831105
for (;;)
10841106
{
1085-
channel_read_json(ch_idx);
1107+
more = channel_parse_json(ch_idx);
10861108

10871109
/* search for messsage "id" */
10881110
if (channel_get_json(ch_idx, id, rettv) == OK)
10891111
return OK;
10901112

1091-
/* Wait for up to 2 seconds.
1092-
* TODO: use timeout set on the channel. */
1093-
if (channel_wait(channels[ch_idx].ch_fd, 2000) == FAIL)
1094-
break;
1095-
channel_read(ch_idx);
1113+
if (!more)
1114+
{
1115+
/* Handle any other messages in the queue. If done some more
1116+
* messages may have arrived. */
1117+
if (channel_parse_messages())
1118+
continue;
1119+
1120+
/* Wait for up to 2 seconds.
1121+
* TODO: use timeout set on the channel. */
1122+
if (channel_wait(channels[ch_idx].ch_fd, 2000) == FAIL)
1123+
break;
1124+
channel_read(ch_idx);
1125+
}
10961126
}
10971127
return FAIL;
10981128
}
@@ -1246,16 +1276,23 @@ channel_select_check(int ret_in, void *rfds_in)
12461276
# endif /* !FEAT_GUI_W32 && HAVE_SELECT */
12471277

12481278
/*
1249-
* Invoked from the main loop when it's save to execute received commands.
1279+
* Execute queued up commands.
1280+
* Invoked from the main loop when it's safe to execute received commands.
1281+
* Return TRUE when something was done.
12501282
*/
1251-
void
1283+
int
12521284
channel_parse_messages(void)
12531285
{
12541286
int i;
1287+
int ret = FALSE;
12551288

12561289
for (i = 0; i < channel_count; ++i)
12571290
while (may_invoke_callback(i) == OK)
1258-
;
1291+
{
1292+
i = 0; /* start over */
1293+
ret = TRUE;
1294+
}
1295+
return ret;
12591296
}
12601297

12611298
#endif /* FEAT_CHANNEL */

src/proto/channel.pro

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@ int channel_open(char *hostname, int port_in, void (*close_cb)(void));
44
void channel_set_json_mode(int idx, int json_mode);
55
void channel_set_callback(int idx, char_u *callback);
66
void channel_set_req_callback(int idx, char_u *callback);
7+
char_u *channel_get(int idx);
8+
int channel_collapse(int idx);
79
int channel_is_open(int idx);
810
void channel_close(int idx);
911
int channel_save(int idx, char_u *buf, int len);
1012
char_u *channel_peek(int idx);
11-
char_u *channel_get(int idx);
12-
int channel_collapse(int idx);
1313
void channel_clear(int idx);
1414
int channel_get_id(void);
1515
void channel_read(int idx);
1616
char_u *channel_read_block(int idx);
1717
int channel_read_json_block(int ch_idx, int id, typval_T **rettv);
18-
void channel_read_json(int ch_idx);
1918
int channel_socket2idx(sock_T fd);
2019
int channel_send(int idx, char_u *buf, char *fun);
2120
int channel_poll_setup(int nfd_in, void *fds_in);
2221
int channel_poll_check(int ret_in, void *fds_in);
2322
int channel_select_setup(int maxfd_in, void *rfds_in);
2423
int channel_select_check(int ret_in, void *rfds_in);
25-
void channel_parse_messages(void);
24+
int channel_parse_messages(void);
2625
/* vim: set ft=c : */

src/testdir/Make_all.mak

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ SCRIPTS_GUI = test16.out
171171
NEW_TESTS = test_arglist.res \
172172
test_assert.res \
173173
test_cdo.res \
174+
test_channel.res \
174175
test_hardcopy.res \
175176
test_increment.res \
176177
test_langmap.res \

src/testdir/test_channel.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/python
2+
#
3+
# Server that will accept connections from a Vim channel.
4+
# Run this server and then in Vim you can open the channel:
5+
# :let handle = ch_open('localhost:8765', 'json')
6+
#
7+
# Then Vim can send requests to the server:
8+
# :let response = ch_sendexpr(handle, 'hello!')
9+
#
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+
#
16+
# See ":help channel-demo" in Vim.
17+
#
18+
# This requires Python 2.6 or later.
19+
20+
from __future__ import print_function
21+
import json
22+
import socket
23+
import sys
24+
import threading
25+
26+
try:
27+
# Python 3
28+
import socketserver
29+
except ImportError:
30+
# Python 2
31+
import SocketServer as socketserver
32+
33+
thesocket = None
34+
35+
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
36+
37+
def handle(self):
38+
print("=== socket opened ===")
39+
global thesocket
40+
thesocket = self.request
41+
while True:
42+
try:
43+
data = self.request.recv(4096).decode('utf-8')
44+
except socket.error:
45+
print("=== socket error ===")
46+
break
47+
except IOError:
48+
print("=== socket closed ===")
49+
break
50+
if data == '':
51+
print("=== socket closed ===")
52+
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)
77+
else:
78+
response = "what?"
79+
80+
encoded = json.dumps([decoded[0], response])
81+
print("sending: {}".format(encoded))
82+
thesocket.sendall(encoded.encode('utf-8'))
83+
84+
thesocket = None
85+
86+
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
87+
pass
88+
89+
if __name__ == "__main__":
90+
HOST, PORT = "localhost", 0
91+
92+
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
93+
ip, port = server.server_address
94+
95+
# Start a thread with the server -- that thread will then start one
96+
# more thread for each request
97+
server_thread = threading.Thread(target=server.serve_forever)
98+
99+
# Exit the server thread when the main thread terminates
100+
server_thread.daemon = True
101+
server_thread.start()
102+
103+
# Write the port number in Xportnr, so that the test knows it.
104+
f = open("Xportnr", "w")
105+
f.write("{}".format(port))
106+
f.close()
107+
108+
# Block here
109+
print("Listening on port {}".format(port))
110+
server.serve_forever()

0 commit comments

Comments
 (0)