blob: e02f7dbcb47328fa3db7cb19275c393242c3a7b7 [file] [log] [blame]
Bram Moolenaarbfe13cc2020-04-12 17:53:12 +02001#!/usr/bin/env python
Bram Moolenaard7ece102016-02-02 23:23:02 +01002#
3# Server that will accept connections from a Vim channel.
Bram Moolenaar77073442016-02-13 23:23:53 +01004# Used by test_channel.vim.
Bram Moolenaard7ece102016-02-02 23:23:02 +01005#
6# This requires Python 2.6 or later.
7
8from __future__ import print_function
9import json
10import socket
11import sys
Bram Moolenaar81661fb2016-02-18 22:23:34 +010012import time
Bram Moolenaard7ece102016-02-02 23:23:02 +010013import threading
14
15try:
16 # Python 3
17 import socketserver
18except ImportError:
19 # Python 2
20 import SocketServer as socketserver
21
LemonBoy1b76a8d2022-04-04 21:13:35 +010022class TestingRequestHandler(socketserver.BaseRequestHandler):
Bram Moolenaard7ece102016-02-02 23:23:02 +010023 def handle(self):
24 print("=== socket opened ===")
Bram Moolenaard7ece102016-02-02 23:23:02 +010025 while True:
26 try:
Bram Moolenaar608a8912016-02-03 22:39:51 +010027 received = self.request.recv(4096).decode('utf-8')
Bram Moolenaard7ece102016-02-02 23:23:02 +010028 except socket.error:
29 print("=== socket error ===")
30 break
31 except IOError:
32 print("=== socket closed ===")
33 break
Bram Moolenaar608a8912016-02-03 22:39:51 +010034 if received == '':
Bram Moolenaard7ece102016-02-02 23:23:02 +010035 print("=== socket closed ===")
36 break
Bram Moolenaara63cdb52016-03-20 18:24:45 +010037 print("received: {0}".format(received))
Bram Moolenaard7ece102016-02-02 23:23:02 +010038
Bram Moolenaare7bed622016-02-03 22:20:29 +010039 # We may receive two messages at once. Take the part up to the
Bram Moolenaarf1f07922016-08-26 17:58:53 +020040 # newline, which should be after the matching "]".
Bram Moolenaar608a8912016-02-03 22:39:51 +010041 todo = received
42 while todo != '':
Bram Moolenaarf1f07922016-08-26 17:58:53 +020043 splitidx = todo.find('\n')
Bram Moolenaare7bed622016-02-03 22:20:29 +010044 if splitidx < 0:
Bram Moolenaar608a8912016-02-03 22:39:51 +010045 used = todo
46 todo = ''
Bram Moolenaard7ece102016-02-02 23:23:02 +010047 else:
Bram Moolenaarf1f07922016-08-26 17:58:53 +020048 used = todo[:splitidx]
Bram Moolenaar608a8912016-02-03 22:39:51 +010049 todo = todo[splitidx + 1:]
50 if used != received:
Bram Moolenaara63cdb52016-03-20 18:24:45 +010051 print("using: {0}".format(used))
Bram Moolenaard7ece102016-02-02 23:23:02 +010052
Bram Moolenaare7bed622016-02-03 22:20:29 +010053 try:
Bram Moolenaar608a8912016-02-03 22:39:51 +010054 decoded = json.loads(used)
Bram Moolenaare7bed622016-02-03 22:20:29 +010055 except ValueError:
56 print("json decoding failed")
57 decoded = [-1, '']
Bram Moolenaard7ece102016-02-02 23:23:02 +010058
Bram Moolenaare7bed622016-02-03 22:20:29 +010059 # Send a response if the sequence number is positive.
60 if decoded[0] >= 0:
61 if decoded[1] == 'hello!':
62 # simply send back a string
63 response = "got it"
Bram Moolenaar5d7ead32018-02-27 17:17:42 +010064 elif decoded[1] == 'malformed1':
65 cmd = '["ex",":"]wrong!["ex","smi"]'
66 print("sending: {0}".format(cmd))
67 self.request.sendall(cmd.encode('utf-8'))
68 response = "ok"
69 # Need to wait for Vim to give up, otherwise it
70 # sometimes fails on OS X.
71 time.sleep(0.2)
72 elif decoded[1] == 'malformed2':
73 cmd = '"unterminated string'
74 print("sending: {0}".format(cmd))
75 self.request.sendall(cmd.encode('utf-8'))
76 response = "ok"
77 # Need to wait for Vim to give up, otherwise the double
78 # quote in the "ok" response terminates the string.
79 time.sleep(0.2)
80 elif decoded[1] == 'malformed3':
81 cmd = '["ex","missing ]"'
82 print("sending: {0}".format(cmd))
83 self.request.sendall(cmd.encode('utf-8'))
84 response = "ok"
85 # Need to wait for Vim to give up, otherwise the ]
86 # in the "ok" response terminates the list.
87 time.sleep(0.2)
88 elif decoded[1] == 'split':
89 cmd = '["ex","let '
90 print("sending: {0}".format(cmd))
91 self.request.sendall(cmd.encode('utf-8'))
92 time.sleep(0.01)
93 cmd = 'g:split = 123"]'
94 print("sending: {0}".format(cmd))
95 self.request.sendall(cmd.encode('utf-8'))
96 response = "ok"
Bram Moolenaard6547fc2016-03-03 19:35:02 +010097 elif decoded[1].startswith("echo "):
98 # send back the argument
99 response = decoded[1][5:]
Bram Moolenaare7bed622016-02-03 22:20:29 +0100100 elif decoded[1] == 'make change':
Bram Moolenaar66624ff2016-02-03 23:59:43 +0100101 # Send two ex commands at the same time, before
102 # replying to the request.
Bram Moolenaare7bed622016-02-03 22:20:29 +0100103 cmd = '["ex","call append(\\"$\\",\\"added1\\")"]'
104 cmd += '["ex","call append(\\"$\\",\\"added2\\")"]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100105 print("sending: {0}".format(cmd))
Bram Moolenaar3b05b132016-02-03 23:25:07 +0100106 self.request.sendall(cmd.encode('utf-8'))
Bram Moolenaare7bed622016-02-03 22:20:29 +0100107 response = "ok"
Bram Moolenaarb836f632021-07-01 22:11:28 +0200108 elif decoded[1] == 'echoerr':
109 cmd = '["ex","echoerr \\\"this is an error\\\""]'
110 print("sending: {0}".format(cmd))
111 self.request.sendall(cmd.encode('utf-8'))
112 response = "ok"
Bram Moolenaar890ee4e2021-07-30 21:56:10 +0200113 # Wait a bit, so that the "ex" command is handled
114 # before the "ch_evalexpr() returns. Otherwise we are
115 # outside the try/catch when the "ex" command is
116 # handled.
117 time.sleep(0.02)
Bram Moolenaarc4dcd602016-03-26 22:56:46 +0100118 elif decoded[1] == 'bad command':
119 cmd = '["ex","foo bar"]'
120 print("sending: {0}".format(cmd))
121 self.request.sendall(cmd.encode('utf-8'))
122 response = "ok"
Bram Moolenaarf4160862016-02-05 23:09:12 +0100123 elif decoded[1] == 'do normal':
124 # Send a normal command.
125 cmd = '["normal","G$s more\u001b"]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100126 print("sending: {0}".format(cmd))
Bram Moolenaarf4160862016-02-05 23:09:12 +0100127 self.request.sendall(cmd.encode('utf-8'))
128 response = "ok"
Bram Moolenaare7bed622016-02-03 22:20:29 +0100129 elif decoded[1] == 'eval-works':
130 # Send an eval request. We ignore the response.
Bram Moolenaarece61b02016-02-20 21:39:05 +0100131 cmd = '["expr","\\"foo\\" . 123", -1]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100132 print("sending: {0}".format(cmd))
Bram Moolenaar3b05b132016-02-03 23:25:07 +0100133 self.request.sendall(cmd.encode('utf-8'))
Bram Moolenaare7bed622016-02-03 22:20:29 +0100134 response = "ok"
Bram Moolenaarfa8b2e12016-03-26 22:19:27 +0100135 elif decoded[1] == 'eval-special':
136 # Send an eval request. We ignore the response.
137 cmd = '["expr","\\"foo\x7f\x10\x01bar\\"", -2]'
138 print("sending: {0}".format(cmd))
139 self.request.sendall(cmd.encode('utf-8'))
140 response = "ok"
141 elif decoded[1] == 'eval-getline':
142 # Send an eval request. We ignore the response.
143 cmd = '["expr","getline(3)", -3]'
144 print("sending: {0}".format(cmd))
145 self.request.sendall(cmd.encode('utf-8'))
146 response = "ok"
Bram Moolenaare7bed622016-02-03 22:20:29 +0100147 elif decoded[1] == 'eval-fails':
148 # Send an eval request that will fail.
Bram Moolenaarfa8b2e12016-03-26 22:19:27 +0100149 cmd = '["expr","xxx", -4]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100150 print("sending: {0}".format(cmd))
Bram Moolenaar3b05b132016-02-03 23:25:07 +0100151 self.request.sendall(cmd.encode('utf-8'))
Bram Moolenaare7bed622016-02-03 22:20:29 +0100152 response = "ok"
Bram Moolenaar55fab432016-02-07 16:53:13 +0100153 elif decoded[1] == 'eval-error':
154 # Send an eval request that works but the result can't
155 # be encoded.
Bram Moolenaarfa8b2e12016-03-26 22:19:27 +0100156 cmd = '["expr","function(\\"tr\\")", -5]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100157 print("sending: {0}".format(cmd))
Bram Moolenaar55fab432016-02-07 16:53:13 +0100158 self.request.sendall(cmd.encode('utf-8'))
159 response = "ok"
Bram Moolenaar66624ff2016-02-03 23:59:43 +0100160 elif decoded[1] == 'eval-bad':
161 # Send an eval request missing the third argument.
Bram Moolenaarece61b02016-02-20 21:39:05 +0100162 cmd = '["expr","xxx"]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100163 print("sending: {0}".format(cmd))
Bram Moolenaar66624ff2016-02-03 23:59:43 +0100164 self.request.sendall(cmd.encode('utf-8'))
165 response = "ok"
Bram Moolenaarf4160862016-02-05 23:09:12 +0100166 elif decoded[1] == 'an expr':
167 # Send an expr request.
168 cmd = '["expr","setline(\\"$\\", [\\"one\\",\\"two\\",\\"three\\"])"]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100169 print("sending: {0}".format(cmd))
Bram Moolenaarf4160862016-02-05 23:09:12 +0100170 self.request.sendall(cmd.encode('utf-8'))
171 response = "ok"
Bram Moolenaarece61b02016-02-20 21:39:05 +0100172 elif decoded[1] == 'call-func':
173 cmd = '["call","MyFunction",[1,2,3], 0]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100174 print("sending: {0}".format(cmd))
Bram Moolenaarece61b02016-02-20 21:39:05 +0100175 self.request.sendall(cmd.encode('utf-8'))
176 response = "ok"
Bram Moolenaarf4160862016-02-05 23:09:12 +0100177 elif decoded[1] == 'redraw':
178 cmd = '["redraw",""]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100179 print("sending: {0}".format(cmd))
Bram Moolenaarf4160862016-02-05 23:09:12 +0100180 self.request.sendall(cmd.encode('utf-8'))
181 response = "ok"
182 elif decoded[1] == 'redraw!':
183 cmd = '["redraw","force"]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100184 print("sending: {0}".format(cmd))
Bram Moolenaarf4160862016-02-05 23:09:12 +0100185 self.request.sendall(cmd.encode('utf-8'))
186 response = "ok"
Bram Moolenaar6076fe12016-02-05 22:49:56 +0100187 elif decoded[1] == 'empty-request':
188 cmd = '[]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100189 print("sending: {0}".format(cmd))
Bram Moolenaar6076fe12016-02-05 22:49:56 +0100190 self.request.sendall(cmd.encode('utf-8'))
191 response = "ok"
Bram Moolenaare7bed622016-02-03 22:20:29 +0100192 elif decoded[1] == 'eval-result':
193 # Send back the last received eval result.
194 response = last_eval
Bram Moolenaarf6157282016-02-10 21:07:14 +0100195 elif decoded[1] == 'call me':
196 cmd = '[0,"we called you"]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100197 print("sending: {0}".format(cmd))
Bram Moolenaarf6157282016-02-10 21:07:14 +0100198 self.request.sendall(cmd.encode('utf-8'))
199 response = "ok"
200 elif decoded[1] == 'call me again':
201 cmd = '[0,"we did call you"]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100202 print("sending: {0}".format(cmd))
Bram Moolenaarf6157282016-02-10 21:07:14 +0100203 self.request.sendall(cmd.encode('utf-8'))
204 response = ""
Bram Moolenaar5983ad02016-03-05 20:54:36 +0100205 elif decoded[1] == 'send zero':
206 cmd = '[0,"zero index"]'
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100207 print("sending: {0}".format(cmd))
Bram Moolenaar5983ad02016-03-05 20:54:36 +0100208 self.request.sendall(cmd.encode('utf-8'))
209 response = "sent zero"
Bram Moolenaar4e221c92016-02-23 13:20:22 +0100210 elif decoded[1] == 'close me':
211 print("closing")
212 self.request.close()
213 response = ""
Bram Moolenaarece61b02016-02-20 21:39:05 +0100214 elif decoded[1] == 'wait a bit':
215 time.sleep(0.2)
216 response = "waited"
Bram Moolenaare7bed622016-02-03 22:20:29 +0100217 elif decoded[1] == '!quit!':
218 # we're done
Bram Moolenaarb3e2f002016-02-04 00:11:37 +0100219 self.server.shutdown()
Bram Moolenaarb92abad2016-02-08 22:37:24 +0100220 return
Bram Moolenaare7bed622016-02-03 22:20:29 +0100221 elif decoded[1] == '!crash!':
222 # Crash!
223 42 / 0
224 else:
225 response = "what?"
226
Bram Moolenaarf6157282016-02-10 21:07:14 +0100227 if response == "":
228 print("no response")
229 else:
230 encoded = json.dumps([decoded[0], response])
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100231 print("sending: {0}".format(encoded))
Bram Moolenaarf6157282016-02-10 21:07:14 +0100232 self.request.sendall(encoded.encode('utf-8'))
Bram Moolenaare7bed622016-02-03 22:20:29 +0100233
234 # Negative numbers are used for "eval" responses.
235 elif decoded[0] < 0:
236 last_eval = decoded
Bram Moolenaarfcb1e3d2016-02-03 21:32:46 +0100237
LemonBoy1b76a8d2022-04-04 21:13:35 +0100238class ThreadedTCPRequestHandler(TestingRequestHandler):
239 def setup(self):
240 self.request.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
241
Bram Moolenaard7ece102016-02-02 23:23:02 +0100242class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
243 pass
244
Bram Moolenaar81661fb2016-02-18 22:23:34 +0100245def writePortInFile(port):
246 # Write the port number in Xportnr, so that the test knows it.
247 f = open("Xportnr", "w")
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100248 f.write("{0}".format(port))
Bram Moolenaar81661fb2016-02-18 22:23:34 +0100249 f.close()
250
Bram Moolenaarbfe13cc2020-04-12 17:53:12 +0200251def main(host, port, server_class=ThreadedTCPServer):
Bram Moolenaar81661fb2016-02-18 22:23:34 +0100252 # Wait half a second before opening the port to test waittime in ch_open().
253 # We do want to get the port number, get that first. We cannot open the
254 # socket, guess a port is free.
255 if len(sys.argv) >= 2 and sys.argv[1] == 'delay':
Bram Moolenaarbfe13cc2020-04-12 17:53:12 +0200256 port = 13684
257 writePortInFile(port)
Bram Moolenaar81661fb2016-02-18 22:23:34 +0100258
259 print("Wait for it...")
260 time.sleep(0.5)
261
James McCoy765d82a2023-01-09 16:25:59 +0000262 addrs = socket.getaddrinfo(host, port, 0, 0, socket.IPPROTO_TCP)
263 # Each addr is a (family, type, proto, canonname, sockaddr) tuple
264 sockaddr = addrs[0][4]
265 server_class.address_family = addrs[0][0]
266
267 server = server_class(sockaddr[0:2], ThreadedTCPRequestHandler)
Bram Moolenaarbfe13cc2020-04-12 17:53:12 +0200268 ip, port = server.server_address[0:2]
Bram Moolenaard7ece102016-02-02 23:23:02 +0100269
Bram Moolenaar6076fe12016-02-05 22:49:56 +0100270 # Start a thread with the server. That thread will then start a new thread
271 # for each connection.
Bram Moolenaard7ece102016-02-02 23:23:02 +0100272 server_thread = threading.Thread(target=server.serve_forever)
Bram Moolenaard7ece102016-02-02 23:23:02 +0100273 server_thread.start()
274
Bram Moolenaar81661fb2016-02-18 22:23:34 +0100275 writePortInFile(port)
Bram Moolenaard7ece102016-02-02 23:23:02 +0100276
Bram Moolenaara63cdb52016-03-20 18:24:45 +0100277 print("Listening on port {0}".format(port))
Bram Moolenaarb3e2f002016-02-04 00:11:37 +0100278
279 # Main thread terminates, but the server continues running
280 # until server.shutdown() is called.
Bram Moolenaarddbe7d22016-02-20 18:26:48 +0100281 try:
Bram Moolenaarbfe13cc2020-04-12 17:53:12 +0200282 while server_thread.is_alive():
Bram Moolenaarddbe7d22016-02-20 18:26:48 +0100283 server_thread.join(1)
284 except (KeyboardInterrupt, SystemExit):
285 server.shutdown()
Bram Moolenaarbfe13cc2020-04-12 17:53:12 +0200286
287if __name__ == "__main__":
Christian Brabandt2a5312e2025-01-19 14:50:31 +0100288 main("127.0.0.1", 0)