xref: /vim-8.2.3635/src/testdir/test_channel.py (revision 94688b8a)
1#!/usr/bin/python
2#
3# Server that will accept connections from a Vim channel.
4# Used by test_channel.vim.
5#
6# This requires Python 2.6 or later.
7
8from __future__ import print_function
9import json
10import socket
11import sys
12import time
13import threading
14
15try:
16    # Python 3
17    import socketserver
18except ImportError:
19    # Python 2
20    import SocketServer as socketserver
21
22class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
23
24    def handle(self):
25        print("=== socket opened ===")
26        while True:
27            try:
28                received = self.request.recv(4096).decode('utf-8')
29            except socket.error:
30                print("=== socket error ===")
31                break
32            except IOError:
33                print("=== socket closed ===")
34                break
35            if received == '':
36                print("=== socket closed ===")
37                break
38            print("received: {0}".format(received))
39
40            # We may receive two messages at once. Take the part up to the
41            # newline, which should be after the matching "]".
42            todo = received
43            while todo != '':
44                splitidx = todo.find('\n')
45                if splitidx < 0:
46                     used = todo
47                     todo = ''
48                else:
49                     used = todo[:splitidx]
50                     todo = todo[splitidx + 1:]
51                if used != received:
52                    print("using: {0}".format(used))
53
54                try:
55                    decoded = json.loads(used)
56                except ValueError:
57                    print("json decoding failed")
58                    decoded = [-1, '']
59
60                # Send a response if the sequence number is positive.
61                if decoded[0] >= 0:
62                    if decoded[1] == 'hello!':
63                        # simply send back a string
64                        response = "got it"
65                    elif decoded[1] == 'malformed1':
66                        cmd = '["ex",":"]wrong!["ex","smi"]'
67                        print("sending: {0}".format(cmd))
68                        self.request.sendall(cmd.encode('utf-8'))
69                        response = "ok"
70                        # Need to wait for Vim to give up, otherwise it
71                        # sometimes fails on OS X.
72                        time.sleep(0.2)
73                    elif decoded[1] == 'malformed2':
74                        cmd = '"unterminated string'
75                        print("sending: {0}".format(cmd))
76                        self.request.sendall(cmd.encode('utf-8'))
77                        response = "ok"
78                        # Need to wait for Vim to give up, otherwise the double
79                        # quote in the "ok" response terminates the string.
80                        time.sleep(0.2)
81                    elif decoded[1] == 'malformed3':
82                        cmd = '["ex","missing ]"'
83                        print("sending: {0}".format(cmd))
84                        self.request.sendall(cmd.encode('utf-8'))
85                        response = "ok"
86                        # Need to wait for Vim to give up, otherwise the ]
87                        # in the "ok" response terminates the list.
88                        time.sleep(0.2)
89                    elif decoded[1] == 'split':
90                        cmd = '["ex","let '
91                        print("sending: {0}".format(cmd))
92                        self.request.sendall(cmd.encode('utf-8'))
93                        time.sleep(0.01)
94                        cmd = 'g:split = 123"]'
95                        print("sending: {0}".format(cmd))
96                        self.request.sendall(cmd.encode('utf-8'))
97                        response = "ok"
98                    elif decoded[1].startswith("echo "):
99                        # send back the argument
100                        response = decoded[1][5:]
101                    elif decoded[1] == 'make change':
102                        # Send two ex commands at the same time, before
103                        # replying to the request.
104                        cmd = '["ex","call append(\\"$\\",\\"added1\\")"]'
105                        cmd += '["ex","call append(\\"$\\",\\"added2\\")"]'
106                        print("sending: {0}".format(cmd))
107                        self.request.sendall(cmd.encode('utf-8'))
108                        response = "ok"
109                    elif decoded[1] == 'bad command':
110                        cmd = '["ex","foo bar"]'
111                        print("sending: {0}".format(cmd))
112                        self.request.sendall(cmd.encode('utf-8'))
113                        response = "ok"
114                    elif decoded[1] == 'do normal':
115                        # Send a normal command.
116                        cmd = '["normal","G$s more\u001b"]'
117                        print("sending: {0}".format(cmd))
118                        self.request.sendall(cmd.encode('utf-8'))
119                        response = "ok"
120                    elif decoded[1] == 'eval-works':
121                        # Send an eval request.  We ignore the response.
122                        cmd = '["expr","\\"foo\\" . 123", -1]'
123                        print("sending: {0}".format(cmd))
124                        self.request.sendall(cmd.encode('utf-8'))
125                        response = "ok"
126                    elif decoded[1] == 'eval-special':
127                        # Send an eval request.  We ignore the response.
128                        cmd = '["expr","\\"foo\x7f\x10\x01bar\\"", -2]'
129                        print("sending: {0}".format(cmd))
130                        self.request.sendall(cmd.encode('utf-8'))
131                        response = "ok"
132                    elif decoded[1] == 'eval-getline':
133                        # Send an eval request.  We ignore the response.
134                        cmd = '["expr","getline(3)", -3]'
135                        print("sending: {0}".format(cmd))
136                        self.request.sendall(cmd.encode('utf-8'))
137                        response = "ok"
138                    elif decoded[1] == 'eval-fails':
139                        # Send an eval request that will fail.
140                        cmd = '["expr","xxx", -4]'
141                        print("sending: {0}".format(cmd))
142                        self.request.sendall(cmd.encode('utf-8'))
143                        response = "ok"
144                    elif decoded[1] == 'eval-error':
145                        # Send an eval request that works but the result can't
146                        # be encoded.
147                        cmd = '["expr","function(\\"tr\\")", -5]'
148                        print("sending: {0}".format(cmd))
149                        self.request.sendall(cmd.encode('utf-8'))
150                        response = "ok"
151                    elif decoded[1] == 'eval-bad':
152                        # Send an eval request missing the third argument.
153                        cmd = '["expr","xxx"]'
154                        print("sending: {0}".format(cmd))
155                        self.request.sendall(cmd.encode('utf-8'))
156                        response = "ok"
157                    elif decoded[1] == 'an expr':
158                        # Send an expr request.
159                        cmd = '["expr","setline(\\"$\\", [\\"one\\",\\"two\\",\\"three\\"])"]'
160                        print("sending: {0}".format(cmd))
161                        self.request.sendall(cmd.encode('utf-8'))
162                        response = "ok"
163                    elif decoded[1] == 'call-func':
164                        cmd = '["call","MyFunction",[1,2,3], 0]'
165                        print("sending: {0}".format(cmd))
166                        self.request.sendall(cmd.encode('utf-8'))
167                        response = "ok"
168                    elif decoded[1] == 'redraw':
169                        cmd = '["redraw",""]'
170                        print("sending: {0}".format(cmd))
171                        self.request.sendall(cmd.encode('utf-8'))
172                        response = "ok"
173                    elif decoded[1] == 'redraw!':
174                        cmd = '["redraw","force"]'
175                        print("sending: {0}".format(cmd))
176                        self.request.sendall(cmd.encode('utf-8'))
177                        response = "ok"
178                    elif decoded[1] == 'empty-request':
179                        cmd = '[]'
180                        print("sending: {0}".format(cmd))
181                        self.request.sendall(cmd.encode('utf-8'))
182                        response = "ok"
183                    elif decoded[1] == 'eval-result':
184                        # Send back the last received eval result.
185                        response = last_eval
186                    elif decoded[1] == 'call me':
187                        cmd = '[0,"we called you"]'
188                        print("sending: {0}".format(cmd))
189                        self.request.sendall(cmd.encode('utf-8'))
190                        response = "ok"
191                    elif decoded[1] == 'call me again':
192                        cmd = '[0,"we did call you"]'
193                        print("sending: {0}".format(cmd))
194                        self.request.sendall(cmd.encode('utf-8'))
195                        response = ""
196                    elif decoded[1] == 'send zero':
197                        cmd = '[0,"zero index"]'
198                        print("sending: {0}".format(cmd))
199                        self.request.sendall(cmd.encode('utf-8'))
200                        response = "sent zero"
201                    elif decoded[1] == 'close me':
202                        print("closing")
203                        self.request.close()
204                        response = ""
205                    elif decoded[1] == 'wait a bit':
206                        time.sleep(0.2)
207                        response = "waited"
208                    elif decoded[1] == '!quit!':
209                        # we're done
210                        self.server.shutdown()
211                        return
212                    elif decoded[1] == '!crash!':
213                        # Crash!
214                        42 / 0
215                    else:
216                        response = "what?"
217
218                    if response == "":
219                        print("no response")
220                    else:
221                        encoded = json.dumps([decoded[0], response])
222                        print("sending: {0}".format(encoded))
223                        self.request.sendall(encoded.encode('utf-8'))
224
225                # Negative numbers are used for "eval" responses.
226                elif decoded[0] < 0:
227                    last_eval = decoded
228
229class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
230    pass
231
232def writePortInFile(port):
233    # Write the port number in Xportnr, so that the test knows it.
234    f = open("Xportnr", "w")
235    f.write("{0}".format(port))
236    f.close()
237
238if __name__ == "__main__":
239    HOST, PORT = "localhost", 0
240
241    # Wait half a second before opening the port to test waittime in ch_open().
242    # We do want to get the port number, get that first.  We cannot open the
243    # socket, guess a port is free.
244    if len(sys.argv) >= 2 and sys.argv[1] == 'delay':
245        PORT = 13684
246        writePortInFile(PORT)
247
248        print("Wait for it...")
249        time.sleep(0.5)
250
251    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
252    ip, port = server.server_address
253
254    # Start a thread with the server.  That thread will then start a new thread
255    # for each connection.
256    server_thread = threading.Thread(target=server.serve_forever)
257    server_thread.start()
258
259    writePortInFile(port)
260
261    print("Listening on port {0}".format(port))
262
263    # Main thread terminates, but the server continues running
264    # until server.shutdown() is called.
265    try:
266        while server_thread.isAlive():
267            server_thread.join(1)
268    except (KeyboardInterrupt, SystemExit):
269        server.shutdown()
270