~bzr-pqm/bzr/bzr.dev

1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
1
# Copyright (C) 2006 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Tests for smart transport"""
18
19
# all of this deals with byte strings so this is safe
20
from cStringIO import StringIO
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
21
import os
22
import socket
23
import threading
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
24
import urllib2
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
25
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
26
from bzrlib import (
27
        bzrdir,
28
        errors,
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
29
        osutils,
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
30
        tests,
2049.1.1 by Lukáš Lalinský
Windows-speficic smart server transport selftest fixes.
31
        urlutils,
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
32
        )
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
33
from bzrlib.tests.HTTPTestUtil import (
34
        HTTPServerWithSmarts,
35
        SmartRequestHandler,
36
        )
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
37
from bzrlib.transport import (
38
        get_transport,
39
        local,
40
        memory,
41
        smart,
42
        )
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
43
from bzrlib.transport.http import SmartClientHTTPMediumRequest
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
44
45
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
46
class StringIOSSHVendor(object):
47
    """A SSH vendor that uses StringIO to buffer writes and answer reads."""
48
49
    def __init__(self, read_from, write_to):
50
        self.read_from = read_from
51
        self.write_to = write_to
52
        self.calls = []
53
54
    def connect_ssh(self, username, password, host, port, command):
55
        self.calls.append(('connect_ssh', username, password, host, port,
56
            command))
57
        return StringIOSSHConnection(self)
58
59
60
class StringIOSSHConnection(object):
61
    """A SSH connection that uses StringIO to buffer writes and answer reads."""
62
63
    def __init__(self, vendor):
64
        self.vendor = vendor
65
    
66
    def close(self):
67
        self.vendor.calls.append(('close', ))
68
        
69
    def get_filelike_channels(self):
70
        return self.vendor.read_from, self.vendor.write_to
71
72
73
74
class SmartClientMediumTests(tests.TestCase):
75
    """Tests for SmartClientMedium.
76
77
    We should create a test scenario for this: we need a server module that
78
    construct the test-servers (like make_loopsocket_and_medium), and the list
79
    of SmartClientMedium classes to test.
80
    """
81
82
    def make_loopsocket_and_medium(self):
83
        """Create a loopback socket for testing, and a medium aimed at it."""
84
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
85
        sock.bind(('127.0.0.1', 0))
86
        sock.listen(1)
87
        port = sock.getsockname()[1]
88
        medium = smart.SmartTCPClientMedium('127.0.0.1', port)
89
        return sock, medium
90
91
    def receive_bytes_on_server(self, sock, bytes):
92
        """Accept a connection on sock and read 3 bytes.
93
94
        The bytes are appended to the list bytes.
95
96
        :return: a Thread which is running to do the accept and recv.
97
        """
98
        def _receive_bytes_on_server():
99
            connection, address = sock.accept()
2091.1.1 by Martin Pool
Avoid MSG_WAITALL as it doesn't work on Windows
100
            bytes.append(osutils.recv_all(connection, 3))
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
101
            connection.close()
102
        t = threading.Thread(target=_receive_bytes_on_server)
103
        t.start()
104
        return t
105
    
106
    def test_construct_smart_stream_medium_client(self):
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
107
        # make a new instance of the common base for Stream-like Mediums.
108
        # this just ensures that the constructor stays parameter-free which
109
        # is important for reuse : some subclasses will dynamically connect,
110
        # others are always on, etc.
111
        medium = smart.SmartClientStreamMedium()
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
112
113
    def test_construct_smart_client_medium(self):
114
        # the base client medium takes no parameters
115
        medium = smart.SmartClientMedium()
116
    
117
    def test_construct_smart_simple_pipes_client_medium(self):
118
        # the SimplePipes client medium takes two pipes:
119
        # readable pipe, writeable pipe.
120
        # Constructing one should just save these and do nothing.
121
        # We test this by passing in None.
122
        medium = smart.SmartSimplePipesClientMedium(None, None)
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
123
        
124
    def test_simple_pipes_client_request_type(self):
125
        # SimplePipesClient should use SmartClientStreamMediumRequest's.
126
        medium = smart.SmartSimplePipesClientMedium(None, None)
127
        request = medium.get_request()
128
        self.assertIsInstance(request, smart.SmartClientStreamMediumRequest)
129
130
    def test_simple_pipes_client_get_concurrent_requests(self):
131
        # the simple_pipes client does not support pipelined requests:
132
        # but it does support serial requests: we construct one after 
133
        # another is finished. This is a smoke test testing the integration
134
        # of the SmartClientStreamMediumRequest and the SmartClientStreamMedium
135
        # classes - as the sibling classes share this logic, they do not have
136
        # explicit tests for this.
137
        output = StringIO()
138
        medium = smart.SmartSimplePipesClientMedium(None, output)
139
        request = medium.get_request()
140
        request.finished_writing()
141
        request.finished_reading()
142
        request2 = medium.get_request()
143
        request2.finished_writing()
144
        request2.finished_reading()
145
146
    def test_simple_pipes_client__accept_bytes_writes_to_writable(self):
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
147
        # accept_bytes writes to the writeable pipe.
148
        output = StringIO()
149
        medium = smart.SmartSimplePipesClientMedium(None, output)
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
150
        medium._accept_bytes('abc')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
151
        self.assertEqual('abc', output.getvalue())
152
    
153
    def test_simple_pipes_client_disconnect_does_nothing(self):
154
        # calling disconnect does nothing.
155
        input = StringIO()
156
        output = StringIO()
157
        medium = smart.SmartSimplePipesClientMedium(input, output)
158
        # send some bytes to ensure disconnecting after activity still does not
159
        # close.
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
160
        medium._accept_bytes('abc')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
161
        medium.disconnect()
162
        self.assertFalse(input.closed)
163
        self.assertFalse(output.closed)
164
165
    def test_simple_pipes_client_accept_bytes_after_disconnect(self):
166
        # calling disconnect on the client does not alter the pipe that
167
        # accept_bytes writes to.
168
        input = StringIO()
169
        output = StringIO()
170
        medium = smart.SmartSimplePipesClientMedium(input, output)
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
171
        medium._accept_bytes('abc')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
172
        medium.disconnect()
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
173
        medium._accept_bytes('abc')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
174
        self.assertFalse(input.closed)
175
        self.assertFalse(output.closed)
176
        self.assertEqual('abcabc', output.getvalue())
177
    
178
    def test_simple_pipes_client_ignores_disconnect_when_not_connected(self):
179
        # Doing a disconnect on a new (and thus unconnected) SimplePipes medium
180
        # does nothing.
181
        medium = smart.SmartSimplePipesClientMedium(None, None)
182
        medium.disconnect()
183
184
    def test_simple_pipes_client_can_always_read(self):
185
        # SmartSimplePipesClientMedium is never disconnected, so read_bytes
186
        # always tries to read from the underlying pipe.
187
        input = StringIO('abcdef')
188
        medium = smart.SmartSimplePipesClientMedium(input, None)
189
        self.assertEqual('abc', medium.read_bytes(3))
190
        medium.disconnect()
191
        self.assertEqual('def', medium.read_bytes(3))
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
192
        
193
    def test_simple_pipes_client_supports__flush(self):
194
        # invoking _flush on a SimplePipesClient should flush the output 
195
        # pipe. We test this by creating an output pipe that records
196
        # flush calls made to it.
197
        from StringIO import StringIO # get regular StringIO
198
        input = StringIO()
199
        output = StringIO()
200
        flush_calls = []
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
201
        def logging_flush(): flush_calls.append('flush')
202
        output.flush = logging_flush
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
203
        medium = smart.SmartSimplePipesClientMedium(input, output)
204
        # this call is here to ensure we only flush once, not on every
205
        # _accept_bytes call.
206
        medium._accept_bytes('abc')
207
        medium._flush()
208
        medium.disconnect()
209
        self.assertEqual(['flush'], flush_calls)
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
210
211
    def test_construct_smart_ssh_client_medium(self):
212
        # the SSH client medium takes:
213
        # host, port, username, password, vendor
214
        # Constructing one should just save these and do nothing.
215
        # we test this by creating a empty bound socket and constructing
216
        # a medium.
217
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
218
        sock.bind(('127.0.0.1', 0))
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
219
        unopened_port = sock.getsockname()[1]
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
220
        # having vendor be invalid means that if it tries to connect via the
221
        # vendor it will blow up.
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
222
        medium = smart.SmartSSHClientMedium('127.0.0.1', unopened_port,
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
223
            username=None, password=None, vendor="not a vendor")
224
        sock.close()
225
226
    def test_ssh_client_connects_on_first_use(self):
227
        # The only thing that initiates a connection from the medium is giving
228
        # it bytes.
229
        output = StringIO()
230
        vendor = StringIOSSHVendor(StringIO(), output)
231
        medium = smart.SmartSSHClientMedium('a hostname', 'a port', 'a username',
232
            'a password', vendor)
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
233
        medium._accept_bytes('abc')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
234
        self.assertEqual('abc', output.getvalue())
235
        self.assertEqual([('connect_ssh', 'a username', 'a password',
236
            'a hostname', 'a port',
237
            ['bzr', 'serve', '--inet', '--directory=/', '--allow-writes'])],
238
            vendor.calls)
239
    
240
    def test_ssh_client_changes_command_when_BZR_REMOTE_PATH_is_set(self):
241
        # The only thing that initiates a connection from the medium is giving
242
        # it bytes.
243
        output = StringIO()
244
        vendor = StringIOSSHVendor(StringIO(), output)
245
        orig_bzr_remote_path = os.environ.get('BZR_REMOTE_PATH')
246
        def cleanup_environ():
247
            osutils.set_or_unset_env('BZR_REMOTE_PATH', orig_bzr_remote_path)
248
        self.addCleanup(cleanup_environ)
249
        os.environ['BZR_REMOTE_PATH'] = 'fugly'
250
        medium = smart.SmartSSHClientMedium('a hostname', 'a port', 'a username',
251
            'a password', vendor)
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
252
        medium._accept_bytes('abc')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
253
        self.assertEqual('abc', output.getvalue())
254
        self.assertEqual([('connect_ssh', 'a username', 'a password',
255
            'a hostname', 'a port',
256
            ['fugly', 'serve', '--inet', '--directory=/', '--allow-writes'])],
257
            vendor.calls)
258
    
259
    def test_ssh_client_disconnect_does_so(self):
260
        # calling disconnect should disconnect both the read_from and write_to
261
        # file-like object it from the ssh connection.
262
        input = StringIO()
263
        output = StringIO()
264
        vendor = StringIOSSHVendor(input, output)
265
        medium = smart.SmartSSHClientMedium('a hostname', vendor=vendor)
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
266
        medium._accept_bytes('abc')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
267
        medium.disconnect()
268
        self.assertTrue(input.closed)
269
        self.assertTrue(output.closed)
270
        self.assertEqual([
271
            ('connect_ssh', None, None, 'a hostname', None,
272
            ['bzr', 'serve', '--inet', '--directory=/', '--allow-writes']),
273
            ('close', ),
274
            ],
275
            vendor.calls)
276
277
    def test_ssh_client_disconnect_allows_reconnection(self):
278
        # calling disconnect on the client terminates the connection, but should
279
        # not prevent additional connections occuring.
280
        # we test this by initiating a second connection after doing a
281
        # disconnect.
282
        input = StringIO()
283
        output = StringIO()
284
        vendor = StringIOSSHVendor(input, output)
285
        medium = smart.SmartSSHClientMedium('a hostname', vendor=vendor)
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
286
        medium._accept_bytes('abc')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
287
        medium.disconnect()
288
        # the disconnect has closed output, so we need a new output for the
289
        # new connection to write to.
290
        input2 = StringIO()
291
        output2 = StringIO()
292
        vendor.read_from = input2
293
        vendor.write_to = output2
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
294
        medium._accept_bytes('abc')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
295
        medium.disconnect()
296
        self.assertTrue(input.closed)
297
        self.assertTrue(output.closed)
298
        self.assertTrue(input2.closed)
299
        self.assertTrue(output2.closed)
300
        self.assertEqual([
301
            ('connect_ssh', None, None, 'a hostname', None,
302
            ['bzr', 'serve', '--inet', '--directory=/', '--allow-writes']),
303
            ('close', ),
304
            ('connect_ssh', None, None, 'a hostname', None,
305
            ['bzr', 'serve', '--inet', '--directory=/', '--allow-writes']),
306
            ('close', ),
307
            ],
308
            vendor.calls)
309
    
310
    def test_ssh_client_ignores_disconnect_when_not_connected(self):
311
        # Doing a disconnect on a new (and thus unconnected) SSH medium
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
312
        # does not fail.  It's ok to disconnect an unconnected medium.
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
313
        medium = smart.SmartSSHClientMedium(None)
314
        medium.disconnect()
315
316
    def test_ssh_client_raises_on_read_when_not_connected(self):
317
        # Doing a read on a new (and thus unconnected) SSH medium raises
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
318
        # MediumNotConnected.
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
319
        medium = smart.SmartSSHClientMedium(None)
320
        self.assertRaises(errors.MediumNotConnected, medium.read_bytes, 0)
321
        self.assertRaises(errors.MediumNotConnected, medium.read_bytes, 1)
322
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
323
    def test_ssh_client_supports__flush(self):
324
        # invoking _flush on a SSHClientMedium should flush the output 
325
        # pipe. We test this by creating an output pipe that records
326
        # flush calls made to it.
327
        from StringIO import StringIO # get regular StringIO
328
        input = StringIO()
329
        output = StringIO()
330
        flush_calls = []
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
331
        def logging_flush(): flush_calls.append('flush')
332
        output.flush = logging_flush
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
333
        vendor = StringIOSSHVendor(input, output)
334
        medium = smart.SmartSSHClientMedium('a hostname', vendor=vendor)
335
        # this call is here to ensure we only flush once, not on every
336
        # _accept_bytes call.
337
        medium._accept_bytes('abc')
338
        medium._flush()
339
        medium.disconnect()
340
        self.assertEqual(['flush'], flush_calls)
341
        
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
342
    def test_construct_smart_tcp_client_medium(self):
343
        # the TCP client medium takes a host and a port.  Constructing it won't
344
        # connect to anything.
345
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
346
        sock.bind(('127.0.0.1', 0))
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
347
        unopened_port = sock.getsockname()[1]
348
        medium = smart.SmartTCPClientMedium('127.0.0.1', unopened_port)
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
349
        sock.close()
350
351
    def test_tcp_client_connects_on_first_use(self):
352
        # The only thing that initiates a connection from the medium is giving
353
        # it bytes.
354
        sock, medium = self.make_loopsocket_and_medium()
355
        bytes = []
356
        t = self.receive_bytes_on_server(sock, bytes)
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
357
        medium.accept_bytes('abc')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
358
        t.join()
359
        sock.close()
360
        self.assertEqual(['abc'], bytes)
361
    
362
    def test_tcp_client_disconnect_does_so(self):
363
        # calling disconnect on the client terminates the connection.
364
        # we test this by forcing a short read during a socket.MSG_WAITALL
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
365
        # call: write 2 bytes, try to read 3, and then the client disconnects.
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
366
        sock, medium = self.make_loopsocket_and_medium()
367
        bytes = []
368
        t = self.receive_bytes_on_server(sock, bytes)
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
369
        medium.accept_bytes('ab')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
370
        medium.disconnect()
371
        t.join()
372
        sock.close()
373
        self.assertEqual(['ab'], bytes)
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
374
        # now disconnect again: this should not do anything, if disconnection
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
375
        # really did disconnect.
376
        medium.disconnect()
377
    
378
    def test_tcp_client_ignores_disconnect_when_not_connected(self):
379
        # Doing a disconnect on a new (and thus unconnected) TCP medium
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
380
        # does not fail.  It's ok to disconnect an unconnected medium.
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
381
        medium = smart.SmartTCPClientMedium(None, None)
382
        medium.disconnect()
383
384
    def test_tcp_client_raises_on_read_when_not_connected(self):
385
        # Doing a read on a new (and thus unconnected) TCP medium raises
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
386
        # MediumNotConnected.
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
387
        medium = smart.SmartTCPClientMedium(None, None)
388
        self.assertRaises(errors.MediumNotConnected, medium.read_bytes, 0)
389
        self.assertRaises(errors.MediumNotConnected, medium.read_bytes, 1)
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
390
391
    def test_tcp_client_supports__flush(self):
392
        # invoking _flush on a TCPClientMedium should do something useful.
393
        # RBC 20060922 not sure how to test/tell in this case.
394
        sock, medium = self.make_loopsocket_and_medium()
395
        bytes = []
396
        t = self.receive_bytes_on_server(sock, bytes)
397
        # try with nothing buffered
398
        medium._flush()
399
        medium._accept_bytes('ab')
400
        # and with something sent.
401
        medium._flush()
402
        medium.disconnect()
403
        t.join()
404
        sock.close()
405
        self.assertEqual(['ab'], bytes)
406
        # now disconnect again : this should not do anything, if disconnection
407
        # really did disconnect.
408
        medium.disconnect()
409
410
411
class TestSmartClientStreamMediumRequest(tests.TestCase):
412
    """Tests the for SmartClientStreamMediumRequest.
413
    
414
    SmartClientStreamMediumRequest is a helper for the three stream based 
415
    mediums: TCP, SSH, SimplePipes, so we only test it once, and then test that
416
    those three mediums implement the interface it expects.
417
    """
418
419
    def test_accept_bytes_after_finished_writing_errors(self):
420
        # calling accept_bytes after calling finished_writing raises 
421
        # WritingCompleted to prevent bad assumptions on stream environments
422
        # breaking the needs of message-based environments.
423
        output = StringIO()
424
        medium = smart.SmartSimplePipesClientMedium(None, output)
425
        request = smart.SmartClientStreamMediumRequest(medium)
426
        request.finished_writing()
427
        self.assertRaises(errors.WritingCompleted, request.accept_bytes, None)
428
429
    def test_accept_bytes(self):
430
        # accept bytes should invoke _accept_bytes on the stream medium.
431
        # we test this by using the SimplePipes medium - the most trivial one
432
        # and checking that the pipes get the data.
433
        input = StringIO()
434
        output = StringIO()
435
        medium = smart.SmartSimplePipesClientMedium(input, output)
436
        request = smart.SmartClientStreamMediumRequest(medium)
437
        request.accept_bytes('123')
438
        request.finished_writing()
439
        request.finished_reading()
440
        self.assertEqual('', input.getvalue())
441
        self.assertEqual('123', output.getvalue())
442
443
    def test_construct_sets_stream_request(self):
444
        # constructing a SmartClientStreamMediumRequest on a StreamMedium sets
445
        # the current request to the new SmartClientStreamMediumRequest
446
        output = StringIO()
447
        medium = smart.SmartSimplePipesClientMedium(None, output)
448
        request = smart.SmartClientStreamMediumRequest(medium)
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
449
        self.assertIs(medium._current_request, request)
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
450
451
    def test_construct_while_another_request_active_throws(self):
452
        # constructing a SmartClientStreamMediumRequest on a StreamMedium with
453
        # a non-None _current_request raises TooManyConcurrentRequests.
454
        output = StringIO()
455
        medium = smart.SmartSimplePipesClientMedium(None, output)
456
        medium._current_request = "a"
457
        self.assertRaises(errors.TooManyConcurrentRequests,
458
            smart.SmartClientStreamMediumRequest, medium)
459
460
    def test_finished_read_clears_current_request(self):
461
        # calling finished_reading clears the current request from the requests
462
        # medium
463
        output = StringIO()
464
        medium = smart.SmartSimplePipesClientMedium(None, output)
465
        request = smart.SmartClientStreamMediumRequest(medium)
466
        request.finished_writing()
467
        request.finished_reading()
468
        self.assertEqual(None, medium._current_request)
469
470
    def test_finished_read_before_finished_write_errors(self):
471
        # calling finished_reading before calling finished_writing triggers a
472
        # WritingNotComplete error.
473
        medium = smart.SmartSimplePipesClientMedium(None, None)
474
        request = smart.SmartClientStreamMediumRequest(medium)
475
        self.assertRaises(errors.WritingNotComplete, request.finished_reading)
476
        
477
    def test_read_bytes(self):
478
        # read bytes should invoke _read_bytes on the stream medium.
479
        # we test this by using the SimplePipes medium - the most trivial one
480
        # and checking that the data is supplied. Its possible that a 
481
        # faulty implementation could poke at the pipe variables them selves,
482
        # but we trust that this will be caught as it will break the integration
483
        # smoke tests.
484
        input = StringIO('321')
485
        output = StringIO()
486
        medium = smart.SmartSimplePipesClientMedium(input, output)
487
        request = smart.SmartClientStreamMediumRequest(medium)
488
        request.finished_writing()
489
        self.assertEqual('321', request.read_bytes(3))
490
        request.finished_reading()
491
        self.assertEqual('', input.read())
492
        self.assertEqual('', output.getvalue())
493
494
    def test_read_bytes_before_finished_write_errors(self):
495
        # calling read_bytes before calling finished_writing triggers a
496
        # WritingNotComplete error because the Smart protocol is designed to be
497
        # compatible with strict message based protocols like HTTP where the
498
        # request cannot be submitted until the writing has completed.
499
        medium = smart.SmartSimplePipesClientMedium(None, None)
500
        request = smart.SmartClientStreamMediumRequest(medium)
501
        self.assertRaises(errors.WritingNotComplete, request.read_bytes, None)
502
503
    def test_read_bytes_after_finished_reading_errors(self):
504
        # calling read_bytes after calling finished_reading raises 
505
        # ReadingCompleted to prevent bad assumptions on stream environments
506
        # breaking the needs of message-based environments.
507
        output = StringIO()
508
        medium = smart.SmartSimplePipesClientMedium(None, output)
509
        request = smart.SmartClientStreamMediumRequest(medium)
510
        request.finished_writing()
511
        request.finished_reading()
512
        self.assertRaises(errors.ReadingCompleted, request.read_bytes, None)
513
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
514
515
class RemoteTransportTests(tests.TestCaseWithTransport):
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
516
517
    def setUp(self):
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
518
        super(RemoteTransportTests, self).setUp()
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
519
        # We're allowed to set  the transport class here, so that we don't use
520
        # the default or a parameterized class, but rather use the
521
        # TestCaseWithTransport infrastructure to set up a smart server and
522
        # transport.
523
        self.transport_server = smart.SmartTCPServer_for_testing
524
525
    def test_plausible_url(self):
526
        self.assert_(self.get_url().startswith('bzr://'))
527
528
    def test_probe_transport(self):
529
        t = self.get_transport()
530
        self.assertIsInstance(t, smart.SmartTransport)
531
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
532
    def test_get_medium_from_transport(self):
533
        """Remote transport has a medium always, which it can return."""
534
        t = self.get_transport()
535
        medium = t.get_smart_medium()
536
        self.assertIsInstance(medium, smart.SmartClientMedium)
537
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
538
2018.2.18 by Andrew Bennetts
Just close the pipe/socket if the medium catches an error, and add tests for medium exception handling.
539
class ErrorRaisingProtocol(object):
540
541
    def __init__(self, exception):
542
        self.exception = exception
543
544
    def next_read_size(self):
545
        raise self.exception
546
547
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
548
class SampleRequest(object):
549
    
550
    def __init__(self, expected_bytes):
551
        self.accepted_bytes = ''
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
552
        self._finished_reading = False
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
553
        self.expected_bytes = expected_bytes
554
        self.excess_buffer = ''
555
556
    def accept_bytes(self, bytes):
557
        self.accepted_bytes += bytes
558
        if self.accepted_bytes.startswith(self.expected_bytes):
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
559
            self._finished_reading = True
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
560
            self.excess_buffer = self.accepted_bytes[len(self.expected_bytes):]
561
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
562
    def next_read_size(self):
563
        if self._finished_reading:
564
            return 0
565
        else:
566
            return 1
567
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
568
569
class TestSmartServerStreamMedium(tests.TestCase):
570
571
    def portable_socket_pair(self):
572
        """Return a pair of TCP sockets connected to each other.
573
        
574
        Unlike socket.socketpair, this should work on Windows.
575
        """
576
        listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
577
        listen_sock.bind(('127.0.0.1', 0))
578
        listen_sock.listen(1)
579
        client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
580
        client_sock.connect(listen_sock.getsockname())
581
        server_sock, addr = listen_sock.accept()
582
        listen_sock.close()
583
        return server_sock, client_sock
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
584
    
585
    def test_smart_query_version(self):
586
        """Feed a canned query version to a server"""
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
587
        # wire-to-wire, using the whole stack
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
588
        to_server = StringIO('hello\n')
589
        from_server = StringIO()
2018.2.27 by Andrew Bennetts
Merge from bzr.dev
590
        transport = local.LocalTransport(urlutils.local_path_to_url('/'))
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
591
        server = smart.SmartServerPipeStreamMedium(
592
            to_server, from_server, transport)
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
593
        protocol = smart.SmartServerRequestProtocolOne(transport,
594
                from_server.write)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
595
        server._serve_one_request(protocol)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
596
        self.assertEqual('ok\0011\n',
597
                         from_server.getvalue())
598
2018.2.31 by Andrew Bennetts
Fix dispatching of smart server requests involving unicode paths.
599
    def test_response_to_canned_get(self):
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
600
        transport = memory.MemoryTransport('memory:///')
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
601
        transport.put_bytes('testfile', 'contents\nof\nfile\n')
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
602
        to_server = StringIO('get\001./testfile\n')
603
        from_server = StringIO()
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
604
        server = smart.SmartServerPipeStreamMedium(
605
            to_server, from_server, transport)
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
606
        protocol = smart.SmartServerRequestProtocolOne(transport,
607
                from_server.write)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
608
        server._serve_one_request(protocol)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
609
        self.assertEqual('ok\n'
610
                         '17\n'
611
                         'contents\nof\nfile\n'
612
                         'done\n',
613
                         from_server.getvalue())
614
2018.2.31 by Andrew Bennetts
Fix dispatching of smart server requests involving unicode paths.
615
    def test_response_to_canned_get_of_utf8(self):
616
        # wire-to-wire, using the whole stack, with a UTF-8 filename.
617
        transport = memory.MemoryTransport('memory:///')
618
        utf8_filename = u'testfile\N{INTERROBANG}'.encode('utf-8')
619
        transport.put_bytes(utf8_filename, 'contents\nof\nfile\n')
620
        to_server = StringIO('get\001' + utf8_filename + '\n')
621
        from_server = StringIO()
622
        server = smart.SmartServerPipeStreamMedium(
623
            to_server, from_server, transport)
624
        protocol = smart.SmartServerRequestProtocolOne(transport,
625
                from_server.write)
626
        server._serve_one_request(protocol)
627
        self.assertEqual('ok\n'
628
                         '17\n'
629
                         'contents\nof\nfile\n'
630
                         'done\n',
631
                         from_server.getvalue())
632
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
633
    def test_pipe_like_stream_with_bulk_data(self):
634
        sample_request_bytes = 'command\n9\nbulk datadone\n'
635
        to_server = StringIO(sample_request_bytes)
636
        from_server = StringIO()
637
        server = smart.SmartServerPipeStreamMedium(to_server, from_server, None)
638
        sample_protocol = SampleRequest(expected_bytes=sample_request_bytes)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
639
        server._serve_one_request(sample_protocol)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
640
        self.assertEqual('', from_server.getvalue())
641
        self.assertEqual(sample_request_bytes, sample_protocol.accepted_bytes)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
642
        self.assertFalse(server.finished)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
643
644
    def test_socket_stream_with_bulk_data(self):
645
        sample_request_bytes = 'command\n9\nbulk datadone\n'
646
        server_sock, client_sock = self.portable_socket_pair()
647
        server = smart.SmartServerSocketStreamMedium(
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
648
            server_sock, None)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
649
        sample_protocol = SampleRequest(expected_bytes=sample_request_bytes)
650
        client_sock.sendall(sample_request_bytes)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
651
        server._serve_one_request(sample_protocol)
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
652
        server_sock.close()
653
        self.assertEqual('', client_sock.recv(1))
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
654
        self.assertEqual(sample_request_bytes, sample_protocol.accepted_bytes)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
655
        self.assertFalse(server.finished)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
656
657
    def test_pipe_like_stream_shutdown_detection(self):
658
        to_server = StringIO('')
659
        from_server = StringIO()
660
        server = smart.SmartServerPipeStreamMedium(to_server, from_server, None)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
661
        server._serve_one_request(SampleRequest('x'))
662
        self.assertTrue(server.finished)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
663
        
664
    def test_socket_stream_shutdown_detection(self):
665
        server_sock, client_sock = self.portable_socket_pair()
666
        client_sock.close()
667
        server = smart.SmartServerSocketStreamMedium(
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
668
            server_sock, None)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
669
        server._serve_one_request(SampleRequest('x'))
670
        self.assertTrue(server.finished)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
671
        
672
    def test_pipe_like_stream_with_two_requests(self):
673
        # If two requests are read in one go, then two calls to
674
        # _serve_one_request should still process both of them as if they had
675
        # been received seperately.
676
        sample_request_bytes = 'command\n'
677
        to_server = StringIO(sample_request_bytes * 2)
678
        from_server = StringIO()
679
        server = smart.SmartServerPipeStreamMedium(to_server, from_server, None)
680
        first_protocol = SampleRequest(expected_bytes=sample_request_bytes)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
681
        server._serve_one_request(first_protocol)
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
682
        self.assertEqual(0, first_protocol.next_read_size())
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
683
        self.assertEqual('', from_server.getvalue())
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
684
        self.assertFalse(server.finished)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
685
        # Make a new protocol, call _serve_one_request with it to collect the
686
        # second request.
687
        second_protocol = SampleRequest(expected_bytes=sample_request_bytes)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
688
        server._serve_one_request(second_protocol)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
689
        self.assertEqual('', from_server.getvalue())
690
        self.assertEqual(sample_request_bytes, second_protocol.accepted_bytes)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
691
        self.assertFalse(server.finished)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
692
        
693
    def test_socket_stream_with_two_requests(self):
694
        # If two requests are read in one go, then two calls to
695
        # _serve_one_request should still process both of them as if they had
696
        # been received seperately.
697
        sample_request_bytes = 'command\n'
698
        server_sock, client_sock = self.portable_socket_pair()
699
        server = smart.SmartServerSocketStreamMedium(
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
700
            server_sock, None)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
701
        first_protocol = SampleRequest(expected_bytes=sample_request_bytes)
702
        # Put two whole requests on the wire.
703
        client_sock.sendall(sample_request_bytes * 2)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
704
        server._serve_one_request(first_protocol)
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
705
        self.assertEqual(0, first_protocol.next_read_size())
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
706
        self.assertFalse(server.finished)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
707
        # Make a new protocol, call _serve_one_request with it to collect the
708
        # second request.
709
        second_protocol = SampleRequest(expected_bytes=sample_request_bytes)
710
        stream_still_open = server._serve_one_request(second_protocol)
711
        self.assertEqual(sample_request_bytes, second_protocol.accepted_bytes)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
712
        self.assertFalse(server.finished)
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
713
        server_sock.close()
714
        self.assertEqual('', client_sock.recv(1))
2018.2.18 by Andrew Bennetts
Just close the pipe/socket if the medium catches an error, and add tests for medium exception handling.
715
716
    def test_pipe_like_stream_error_handling(self):
717
        # Use plain python StringIO so we can monkey-patch the close method to
718
        # not discard the contents.
719
        from StringIO import StringIO
720
        to_server = StringIO('')
721
        from_server = StringIO()
722
        self.closed = False
723
        def close():
724
            self.closed = True
725
        from_server.close = close
726
        server = smart.SmartServerPipeStreamMedium(to_server, from_server, None)
727
        fake_protocol = ErrorRaisingProtocol(Exception('boom'))
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
728
        server._serve_one_request(fake_protocol)
2018.2.18 by Andrew Bennetts
Just close the pipe/socket if the medium catches an error, and add tests for medium exception handling.
729
        self.assertEqual('', from_server.getvalue())
730
        self.assertTrue(self.closed)
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
731
        self.assertTrue(server.finished)
2018.2.18 by Andrew Bennetts
Just close the pipe/socket if the medium catches an error, and add tests for medium exception handling.
732
        
733
    def test_socket_stream_error_handling(self):
734
        # Use plain python StringIO so we can monkey-patch the close method to
735
        # not discard the contents.
736
        from StringIO import StringIO
737
        server_sock, client_sock = self.portable_socket_pair()
738
        server = smart.SmartServerSocketStreamMedium(
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
739
            server_sock, None)
2018.2.18 by Andrew Bennetts
Just close the pipe/socket if the medium catches an error, and add tests for medium exception handling.
740
        fake_protocol = ErrorRaisingProtocol(Exception('boom'))
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
741
        server._serve_one_request(fake_protocol)
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
742
        # recv should not block, because the other end of the socket has been
743
        # closed.
744
        self.assertEqual('', client_sock.recv(1))
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
745
        self.assertTrue(server.finished)
2018.2.18 by Andrew Bennetts
Just close the pipe/socket if the medium catches an error, and add tests for medium exception handling.
746
        
747
    def test_pipe_like_stream_keyboard_interrupt_handling(self):
748
        # Use plain python StringIO so we can monkey-patch the close method to
749
        # not discard the contents.
750
        to_server = StringIO('')
751
        from_server = StringIO()
752
        server = smart.SmartServerPipeStreamMedium(to_server, from_server, None)
753
        fake_protocol = ErrorRaisingProtocol(KeyboardInterrupt('boom'))
754
        self.assertRaises(
755
            KeyboardInterrupt, server._serve_one_request, fake_protocol)
756
        self.assertEqual('', from_server.getvalue())
757
758
    def test_socket_stream_keyboard_interrupt_handling(self):
759
        server_sock, client_sock = self.portable_socket_pair()
760
        server = smart.SmartServerSocketStreamMedium(
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
761
            server_sock, None)
2018.2.18 by Andrew Bennetts
Just close the pipe/socket if the medium catches an error, and add tests for medium exception handling.
762
        fake_protocol = ErrorRaisingProtocol(KeyboardInterrupt('boom'))
763
        self.assertRaises(
764
            KeyboardInterrupt, server._serve_one_request, fake_protocol)
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
765
        server_sock.close()
766
        self.assertEqual('', client_sock.recv(1))
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
767
        
2018.2.20 by Andrew Bennetts
Tidy up _serve_one_request.
768
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
769
class TestSmartTCPServer(tests.TestCase):
770
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
771
    def test_get_error_unexpected(self):
772
        """Error reported by server with no specific representation"""
773
        class FlakyTransport(object):
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
774
            def get_bytes(self, path):
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
775
                raise Exception("some random exception from inside server")
776
        server = smart.SmartTCPServer(backing_transport=FlakyTransport())
777
        server.start_background_thread()
778
        try:
779
            transport = smart.SmartTCPTransport(server.get_url())
780
            try:
781
                transport.get('something')
782
            except errors.TransportError, e:
783
                self.assertContainsRe(str(e), 'some random exception')
784
            else:
785
                self.fail("get did not raise expected error")
786
        finally:
787
            server.stop_background_thread()
788
789
790
class SmartTCPTests(tests.TestCase):
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
791
    """Tests for connection/end to end behaviour using the TCP server.
792
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
793
    All of these tests are run with a server running on another thread serving
794
    a MemoryTransport, and a connection to it already open.
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
795
796
    the server is obtained by calling self.setUpServer(readonly=False).
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
797
    """
798
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
799
    def setUpServer(self, readonly=False):
800
        """Setup the server.
801
802
        :param readonly: Create a readonly server.
803
        """
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
804
        self.backing_transport = memory.MemoryTransport()
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
805
        if readonly:
806
            self.real_backing_transport = self.backing_transport
807
            self.backing_transport = get_transport("readonly+" + self.backing_transport.abspath('.'))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
808
        self.server = smart.SmartTCPServer(self.backing_transport)
809
        self.server.start_background_thread()
810
        self.transport = smart.SmartTCPTransport(self.server.get_url())
811
812
    def tearDown(self):
813
        if getattr(self, 'transport', None):
814
            self.transport.disconnect()
815
        if getattr(self, 'server', None):
816
            self.server.stop_background_thread()
817
        super(SmartTCPTests, self).tearDown()
818
        
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
819
820
class WritableEndToEndTests(SmartTCPTests):
821
    """Client to server tests that require a writable transport."""
822
823
    def setUp(self):
824
        super(WritableEndToEndTests, self).setUp()
825
        self.setUpServer()
826
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
827
    def test_start_tcp_server(self):
828
        url = self.server.get_url()
829
        self.assertContainsRe(url, r'^bzr://127\.0\.0\.1:[0-9]{2,}/')
830
831
    def test_smart_transport_has(self):
832
        """Checking for file existence over smart."""
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
833
        self.backing_transport.put_bytes("foo", "contents of foo\n")
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
834
        self.assertTrue(self.transport.has("foo"))
835
        self.assertFalse(self.transport.has("non-foo"))
836
837
    def test_smart_transport_get(self):
838
        """Read back a file over smart."""
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
839
        self.backing_transport.put_bytes("foo", "contents\nof\nfoo\n")
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
840
        fp = self.transport.get("foo")
841
        self.assertEqual('contents\nof\nfoo\n', fp.read())
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
842
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
843
    def test_get_error_enoent(self):
844
        """Error reported from server getting nonexistent file."""
1910.19.3 by Andrew Bennetts
Add SSH support.
845
        # The path in a raised NoSuchFile exception should be the precise path
846
        # asked for by the client. This gives meaningful and unsurprising errors
847
        # for users.
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
848
        try:
1910.19.3 by Andrew Bennetts
Add SSH support.
849
            self.transport.get('not%20a%20file')
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
850
        except errors.NoSuchFile, e:
1910.19.3 by Andrew Bennetts
Add SSH support.
851
            self.assertEqual('not%20a%20file', e.path)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
852
        else:
853
            self.fail("get did not raise expected error")
854
855
    def test_simple_clone_conn(self):
856
        """Test that cloning reuses the same connection."""
857
        # we create a real connection not a loopback one, but it will use the
858
        # same server and pipes
1910.19.3 by Andrew Bennetts
Add SSH support.
859
        conn2 = self.transport.clone('.')
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
860
        self.assertIs(self.transport._medium, conn2._medium)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
861
1910.19.12 by Andrew Bennetts
Activate a disabled test, rename another test to be consistent with what it's testing. (Andrew Bennetts, Robert Collins)
862
    def test__remote_path(self):
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
863
        self.assertEquals('/foo/bar',
864
                          self.transport._remote_path('foo/bar'))
865
866
    def test_clone_changes_base(self):
867
        """Cloning transport produces one with a new base location"""
868
        conn2 = self.transport.clone('subdir')
869
        self.assertEquals(self.transport.base + 'subdir/',
870
                          conn2.base)
871
872
    def test_open_dir(self):
873
        """Test changing directory"""
874
        transport = self.transport
875
        self.backing_transport.mkdir('toffee')
876
        self.backing_transport.mkdir('toffee/apple')
877
        self.assertEquals('/toffee', transport._remote_path('toffee'))
1910.19.13 by Andrew Bennetts
Address various review comments.
878
        toffee_trans = transport.clone('toffee')
879
        # Check that each transport has only the contents of its directory
880
        # directly visible. If state was being held in the wrong object, it's
881
        # conceivable that cloning a transport would alter the state of the
882
        # cloned-from transport.
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
883
        self.assertTrue(transport.has('toffee'))
1910.19.13 by Andrew Bennetts
Address various review comments.
884
        self.assertFalse(toffee_trans.has('toffee'))
885
        self.assertFalse(transport.has('apple'))
886
        self.assertTrue(toffee_trans.has('apple'))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
887
888
    def test_open_bzrdir(self):
889
        """Open an existing bzrdir over smart transport"""
890
        transport = self.transport
891
        t = self.backing_transport
892
        bzrdir.BzrDirFormat.get_default_format().initialize_on_transport(t)
893
        result_dir = bzrdir.BzrDir.open_containing_from_transport(transport)
894
895
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
896
class ReadOnlyEndToEndTests(SmartTCPTests):
897
    """Tests from the client to the server using a readonly backing transport."""
898
899
    def test_mkdir_error_readonly(self):
900
        """TransportNotPossible should be preserved from the backing transport."""
901
        self.setUpServer(readonly=True)
902
        self.assertRaises(errors.TransportNotPossible, self.transport.mkdir,
903
            'foo')
904
        
905
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
906
class SmartServerRequestHandlerTests(tests.TestCaseWithTransport):
907
    """Test that call directly into the handler logic, bypassing the network."""
908
909
    def test_construct_request_handler(self):
910
        """Constructing a request handler should be easy and set defaults."""
911
        handler = smart.SmartServerRequestHandler(None)
912
        self.assertFalse(handler.finished_reading)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
913
914
    def test_hello(self):
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
915
        handler = smart.SmartServerRequestHandler(None)
916
        handler.dispatch_command('hello', ())
917
        self.assertEqual(('ok', '1'), handler.response.args)
918
        self.assertEqual(None, handler.response.body)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
919
        
920
    def test_get_bundle(self):
921
        from bzrlib.bundle import serializer
922
        wt = self.make_branch_and_tree('.')
1910.19.13 by Andrew Bennetts
Address various review comments.
923
        self.build_tree_contents([('hello', 'hello world')])
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
924
        wt.add('hello')
1910.19.13 by Andrew Bennetts
Address various review comments.
925
        rev_id = wt.commit('add hello')
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
926
        
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
927
        handler = smart.SmartServerRequestHandler(self.get_transport())
928
        handler.dispatch_command('get_bundle', ('.', rev_id))
929
        bundle = serializer.read_bundle(StringIO(handler.response.body))
930
        self.assertEqual((), handler.response.args)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
931
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
932
    def test_readonly_exception_becomes_transport_not_possible(self):
933
        """The response for a read-only error is ('ReadOnlyError')."""
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
934
        handler = smart.SmartServerRequestHandler(self.get_readonly_transport())
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
935
        # send a mkdir for foo, with no explicit mode - should fail.
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
936
        handler.dispatch_command('mkdir', ('foo', ''))
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
937
        # and the failure should be an explicit ReadOnlyError
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
938
        self.assertEqual(("ReadOnlyError", ), handler.response.args)
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
939
        # XXX: TODO: test that other TransportNotPossible errors are
940
        # presented as TransportNotPossible - not possible to do that
941
        # until I figure out how to trigger that relatively cleanly via
942
        # the api. RBC 20060918
943
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
944
    def test_hello_has_finished_body_on_dispatch(self):
945
        """The 'hello' command should set finished_reading."""
946
        handler = smart.SmartServerRequestHandler(None)
947
        handler.dispatch_command('hello', ())
948
        self.assertTrue(handler.finished_reading)
949
        self.assertNotEqual(None, handler.response)
950
951
    def test_put_bytes_non_atomic(self):
952
        """'put_...' should set finished_reading after reading the bytes."""
953
        handler = smart.SmartServerRequestHandler(self.get_transport())
2018.2.36 by Andrew Bennetts
Don't UTF-8 decode paths in requests. They should be url-quoted (and thus
954
        handler.dispatch_command('put_non_atomic', ('a-file', '', 'F', ''))
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
955
        self.assertFalse(handler.finished_reading)
956
        handler.accept_body('1234')
957
        self.assertFalse(handler.finished_reading)
958
        handler.accept_body('5678')
959
        handler.end_of_body()
960
        self.assertTrue(handler.finished_reading)
961
        self.assertEqual(('ok', ), handler.response.args)
962
        self.assertEqual(None, handler.response.body)
963
        
964
    def test_readv_accept_body(self):
965
        """'readv' should set finished_reading after reading offsets."""
966
        self.build_tree(['a-file'])
967
        handler = smart.SmartServerRequestHandler(self.get_readonly_transport())
2018.2.36 by Andrew Bennetts
Don't UTF-8 decode paths in requests. They should be url-quoted (and thus
968
        handler.dispatch_command('readv', ('a-file', ))
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
969
        self.assertFalse(handler.finished_reading)
970
        handler.accept_body('2,')
971
        self.assertFalse(handler.finished_reading)
972
        handler.accept_body('3')
973
        handler.end_of_body()
974
        self.assertTrue(handler.finished_reading)
975
        self.assertEqual(('readv', ), handler.response.args)
976
        # co - nte - nt of a-file is the file contents we are extracting from.
977
        self.assertEqual('nte', handler.response.body)
978
979
    def test_readv_short_read_response_contents(self):
980
        """'readv' when a short read occurs sets the response appropriately."""
981
        self.build_tree(['a-file'])
982
        handler = smart.SmartServerRequestHandler(self.get_readonly_transport())
2018.2.36 by Andrew Bennetts
Don't UTF-8 decode paths in requests. They should be url-quoted (and thus
983
        handler.dispatch_command('readv', ('a-file', ))
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
984
        # read beyond the end of the file.
985
        handler.accept_body('100,1')
986
        handler.end_of_body()
987
        self.assertTrue(handler.finished_reading)
988
        self.assertEqual(('ShortReadvError', 'a-file', '100', '1', '0'),
989
            handler.response.args)
990
        self.assertEqual(None, handler.response.body)
991
1910.19.3 by Andrew Bennetts
Add SSH support.
992
993
class SmartTransportRegistration(tests.TestCase):
994
995
    def test_registration(self):
996
        t = get_transport('bzr+ssh://example.com/path')
997
        self.assertIsInstance(t, smart.SmartSSHTransport)
998
        self.assertEqual('example.com', t._host)
999
1000
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
1001
class TestSmartTransport(tests.TestCase):
1002
        
1003
    def test_use_connection_factory(self):
1004
        # We want to be able to pass a client as a parameter to SmartTransport.
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1005
        input = StringIO("ok\n3\nbardone\n")
1006
        output = StringIO()
1007
        medium = smart.SmartSimplePipesClientMedium(input, output)
1008
        transport = smart.SmartTransport('bzr://localhost/', medium=medium)
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
1009
1010
        # We want to make sure the client is used when the first remote
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1011
        # method is called.  No data should have been sent, or read.
1012
        self.assertEqual(0, input.tell())
1013
        self.assertEqual('', output.getvalue())
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
1014
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1015
        # Now call a method that should result in a single request : as the
1016
        # transport makes its own protocol instances, we check on the wire.
1017
        # XXX: TODO: give the transport a protocol factory, which can make
1018
        # an instrumented protocol for us.
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
1019
        self.assertEqual('bar', transport.get_bytes('foo'))
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1020
        # only the needed data should have been sent/received.
1021
        self.assertEqual(13, input.tell())
1022
        self.assertEqual('get\x01/foo\n', output.getvalue())
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
1023
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
1024
    def test__translate_error_readonly(self):
1025
        """Sending a ReadOnlyError to _translate_error raises TransportNotPossible."""
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1026
        medium = smart.SmartClientMedium()
1027
        transport = smart.SmartTransport('bzr://localhost/', medium=medium)
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
1028
        self.assertRaises(errors.TransportNotPossible,
1029
            transport._translate_error, ("ReadOnlyError", ))
1030
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
1031
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1032
class InstrumentedServerProtocol(smart.SmartServerStreamMedium):
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
1033
    """A smart server which is backed by memory and saves its write requests."""
1034
1035
    def __init__(self, write_output_list):
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
1036
        smart.SmartServerStreamMedium.__init__(self, memory.MemoryTransport())
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
1037
        self._write_output_list = write_output_list
1038
1039
1040
class TestSmartProtocol(tests.TestCase):
1041
    """Tests for the smart protocol.
1042
1043
    Each test case gets a smart_server and smart_client created during setUp().
1044
1045
    It is planned that the client can be called with self.call_client() giving
1046
    it an expected server response, which will be fed into it when it tries to
1047
    read. Likewise, self.call_server will call a servers method with a canned
1048
    serialised client request. Output done by the client or server for these
1049
    calls will be captured to self.to_server and self.to_client. Each element
1050
    in the list is a write call from the client or server respectively.
1051
    """
1052
1053
    def setUp(self):
1054
        super(TestSmartProtocol, self).setUp()
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1055
        self.server_to_client = []
1056
        self.to_server = StringIO()
1057
        self.to_client = StringIO()
1058
        self.client_medium = smart.SmartSimplePipesClientMedium(self.to_client,
1059
            self.to_server)
1060
        self.client_protocol = smart.SmartClientRequestProtocolOne(
1061
            self.client_medium)
1062
        self.smart_server = InstrumentedServerProtocol(self.server_to_client)
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1063
        self.smart_server_request = smart.SmartServerRequestHandler(None)
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
1064
1065
    def assertOffsetSerialisation(self, expected_offsets, expected_serialised,
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1066
        client, smart_server_request):
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
1067
        """Check that smart (de)serialises offsets as expected.
1068
        
1069
        We check both serialisation and deserialisation at the same time
1070
        to ensure that the round tripping cannot skew: both directions should
1071
        be as expected.
1072
        
1073
        :param expected_offsets: a readv offset list.
1074
        :param expected_seralised: an expected serial form of the offsets.
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1075
        :param smart_server_request: a SmartServerRequestHandler instance.
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
1076
        """
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1077
        # XXX: 'smart_server_request' should be a SmartServerRequestProtocol in
1078
        # future.
1079
        offsets = smart_server_request._deserialise_offsets(expected_serialised)
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
1080
        self.assertEqual(expected_offsets, offsets)
1081
        serialised = client._serialise_offsets(offsets)
1082
        self.assertEqual(expected_serialised, serialised)
1083
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1084
    def build_protocol_waiting_for_body(self):
1085
        out_stream = StringIO()
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
1086
        protocol = smart.SmartServerRequestProtocolOne(None, out_stream.write)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1087
        protocol.has_dispatched = True
1088
        protocol.request = smart.SmartServerRequestHandler(None)
1089
        def handle_end_of_bytes():
1090
            self.end_received = True
1091
            self.assertEqual('abcdefg', protocol.request._body_bytes)
1092
            protocol.request.response = smart.SmartServerResponse(('ok', ))
1093
        protocol.request._end_of_body_handler = handle_end_of_bytes
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
1094
        # Call accept_bytes to make sure that internal state like _body_decoder
1095
        # is initialised.  This test should probably be given a clearer
1096
        # interface to work with that will not cause this inconsistency.
1097
        #   -- Andrew Bennetts, 2006-09-28
1098
        protocol.accept_bytes('')
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1099
        return protocol
1100
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1101
    def test_construct_version_one_server_protocol(self):
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1102
        protocol = smart.SmartServerRequestProtocolOne(None, None)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1103
        self.assertEqual('', protocol.excess_buffer)
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1104
        self.assertEqual('', protocol.in_buffer)
1105
        self.assertFalse(protocol.has_dispatched)
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
1106
        self.assertEqual(1, protocol.next_read_size())
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1107
1108
    def test_construct_version_one_client_protocol(self):
1109
        # we can construct a client protocol from a client medium request
1110
        output = StringIO()
1111
        medium = smart.SmartSimplePipesClientMedium(None, output)
1112
        request = medium.get_request()
1113
        client_protocol = smart.SmartClientRequestProtocolOne(request)
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1114
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
1115
    def test_server_offset_serialisation(self):
1116
        """The Smart protocol serialises offsets as a comma and \n string.
1117
1118
        We check a number of boundary cases are as expected: empty, one offset,
1119
        one with the order of reads not increasing (an out of order read), and
1120
        one that should coalesce.
1121
        """
1122
        self.assertOffsetSerialisation([], '',
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1123
            self.client_protocol, self.smart_server_request)
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
1124
        self.assertOffsetSerialisation([(1,2)], '1,2',
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1125
            self.client_protocol, self.smart_server_request)
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
1126
        self.assertOffsetSerialisation([(10,40), (0,5)], '10,40\n0,5',
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1127
            self.client_protocol, self.smart_server_request)
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
1128
        self.assertOffsetSerialisation([(1,2), (3,4), (100, 200)],
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1129
            '1,2\n3,4\n100,200', self.client_protocol, self.smart_server_request)
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1130
2018.3.2 by Andrew Bennetts
Ensure that a request's next_read_size() is 0 once an error response is sent.
1131
    def test_accept_bytes_of_bad_request_to_protocol(self):
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1132
        out_stream = StringIO()
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
1133
        protocol = smart.SmartServerRequestProtocolOne(None, out_stream.write)
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1134
        protocol.accept_bytes('abc')
1135
        self.assertEqual('abc', protocol.in_buffer)
1136
        protocol.accept_bytes('\n')
1137
        self.assertEqual("error\x01Generic bzr smart protocol error: bad request"
2018.2.36 by Andrew Bennetts
Don't UTF-8 decode paths in requests. They should be url-quoted (and thus
1138
            " 'abc'\n", out_stream.getvalue())
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
1139
        self.assertTrue(protocol.has_dispatched)
2018.3.2 by Andrew Bennetts
Ensure that a request's next_read_size() is 0 once an error response is sent.
1140
        self.assertEqual(0, protocol.next_read_size())
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
1141
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1142
    def test_accept_body_bytes_to_protocol(self):
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1143
        protocol = self.build_protocol_waiting_for_body()
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
1144
        self.assertEqual(6, protocol.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1145
        protocol.accept_bytes('7\nabc')
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
1146
        self.assertEqual(9, protocol.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1147
        protocol.accept_bytes('defgd')
1148
        protocol.accept_bytes('one\n')
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
1149
        self.assertEqual(0, protocol.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1150
        self.assertTrue(self.end_received)
1151
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
1152
    def test_accept_request_and_body_all_at_once(self):
1153
        mem_transport = memory.MemoryTransport()
1154
        mem_transport.put_bytes('foo', 'abcdefghij')
1155
        out_stream = StringIO()
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
1156
        protocol = smart.SmartServerRequestProtocolOne(mem_transport,
1157
                out_stream.write)
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
1158
        protocol.accept_bytes('readv\x01foo\n3\n3,3done\n')
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
1159
        self.assertEqual(0, protocol.next_read_size())
2018.2.14 by Andrew Bennetts
Seperate SmartServer{Pipe,Socket}StreamMedium out of SmartServerStreamMedium. Use recv to make the socket server medium better.
1160
        self.assertEqual('readv\n3\ndefdone\n', out_stream.getvalue())
1161
        self.assertEqual('', protocol.excess_buffer)
1162
        self.assertEqual('', protocol.in_buffer)
1163
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1164
    def test_accept_excess_bytes_are_preserved(self):
1165
        out_stream = StringIO()
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
1166
        protocol = smart.SmartServerRequestProtocolOne(None, out_stream.write)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1167
        protocol.accept_bytes('hello\nhello\n')
1168
        self.assertEqual("ok\x011\n", out_stream.getvalue())
1169
        self.assertEqual("hello\n", protocol.excess_buffer)
1170
        self.assertEqual("", protocol.in_buffer)
1171
1172
    def test_accept_excess_bytes_after_body(self):
1173
        protocol = self.build_protocol_waiting_for_body()
1174
        protocol.accept_bytes('7\nabcdefgdone\nX')
1175
        self.assertTrue(self.end_received)
1176
        self.assertEqual("X", protocol.excess_buffer)
1177
        self.assertEqual("", protocol.in_buffer)
1178
        protocol.accept_bytes('Y')
1179
        self.assertEqual("XY", protocol.excess_buffer)
1180
        self.assertEqual("", protocol.in_buffer)
1181
1182
    def test_accept_excess_bytes_after_dispatch(self):
1183
        out_stream = StringIO()
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
1184
        protocol = smart.SmartServerRequestProtocolOne(None, out_stream.write)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1185
        protocol.accept_bytes('hello\n')
1186
        self.assertEqual("ok\x011\n", out_stream.getvalue())
1187
        protocol.accept_bytes('hel')
1188
        self.assertEqual("hel", protocol.excess_buffer)
1189
        protocol.accept_bytes('lo\n')
1190
        self.assertEqual("hello\n", protocol.excess_buffer)
1191
        self.assertEqual("", protocol.in_buffer)
1192
2018.3.2 by Andrew Bennetts
Ensure that a request's next_read_size() is 0 once an error response is sent.
1193
    def test__send_response_sets_finished_reading(self):
1194
        protocol = smart.SmartServerRequestProtocolOne(None, lambda x: None)
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
1195
        self.assertEqual(1, protocol.next_read_size())
2018.3.2 by Andrew Bennetts
Ensure that a request's next_read_size() is 0 once an error response is sent.
1196
        protocol._send_response(('x',))
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
1197
        self.assertEqual(0, protocol.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1198
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
1199
    def test_query_version(self):
1200
        """query_version on a SmartClientProtocolOne should return a number.
1201
        
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1202
        The protocol provides the query_version because the domain level clients
1203
        may all need to be able to probe for capabilities.
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
1204
        """
1205
        # What we really want to test here is that SmartClientProtocolOne calls
1206
        # accept_bytes(tuple_based_encoding_of_hello) and reads and parses the
1207
        # response of tuple-encoded (ok, 1).  Also, seperately we should test
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1208
        # the error if the response is a non-understood version.
1209
        input = StringIO('ok\x011\n')
1210
        output = StringIO()
1211
        medium = smart.SmartSimplePipesClientMedium(input, output)
1212
        protocol = smart.SmartClientRequestProtocolOne(medium.get_request())
1213
        self.assertEqual(1, protocol.query_version())
1214
1215
    def assertServerToClientEncoding(self, expected_bytes, expected_tuple,
1216
            input_tuples):
1217
        """Assert that each input_tuple serialises as expected_bytes, and the
1218
        bytes deserialise as expected_tuple.
1219
        """
1220
        # check the encoding of the server for all input_tuples matches
1221
        # expected bytes
1222
        for input_tuple in input_tuples:
1223
            server_output = StringIO()
2018.2.23 by Andrew Bennetts
Clean up SmartServerStreamMedium implementations, including removing unnecessary flushes.
1224
            server_protocol = smart.SmartServerRequestProtocolOne(
1225
                None, server_output.write)
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1226
            server_protocol._send_response(input_tuple)
1227
            self.assertEqual(expected_bytes, server_output.getvalue())
1228
        # check the decoding of the client protocol from expected_bytes:
1229
        input = StringIO(expected_bytes)
1230
        output = StringIO()
1231
        medium = smart.SmartSimplePipesClientMedium(input, output)
1232
        protocol = smart.SmartClientRequestProtocolOne(medium.get_request())
1233
        protocol.call('foo')
1234
        self.assertEqual(expected_tuple, protocol.read_response_tuple())
1235
1236
    def test_client_call_empty_response(self):
1237
        # protocol.call() can get back an empty tuple as a response. This occurs
1238
        # when the parsed line is an empty line, and results in a tuple with
1239
        # one element - an empty string.
1240
        self.assertServerToClientEncoding('\n', ('', ), [(), ('', )])
1241
1242
    def test_client_call_three_element_response(self):
1243
        # protocol.call() can get back tuples of other lengths. A three element
1244
        # tuple should be unpacked as three strings.
1245
        self.assertServerToClientEncoding('a\x01b\x0134\n', ('a', 'b', '34'),
1246
            [('a', 'b', '34')])
1247
1248
    def test_client_call_with_body_bytes_uploads(self):
1249
        # protocol.call_with_upload should length-prefix the bytes onto the 
1250
        # wire.
1251
        expected_bytes = "foo\n7\nabcdefgdone\n"
1252
        input = StringIO("\n")
1253
        output = StringIO()
1254
        medium = smart.SmartSimplePipesClientMedium(input, output)
1255
        protocol = smart.SmartClientRequestProtocolOne(medium.get_request())
1256
        protocol.call_with_body_bytes(('foo', ), "abcdefg")
1257
        self.assertEqual(expected_bytes, output.getvalue())
1258
1259
    def test_client_call_with_body_readv_array(self):
1260
        # protocol.call_with_upload should encode the readv array and then
1261
        # length-prefix the bytes onto the wire.
1262
        expected_bytes = "foo\n7\n1,2\n5,6done\n"
1263
        input = StringIO("\n")
1264
        output = StringIO()
1265
        medium = smart.SmartSimplePipesClientMedium(input, output)
1266
        protocol = smart.SmartClientRequestProtocolOne(medium.get_request())
1267
        protocol.call_with_body_readv_array(('foo', ), [(1,2),(5,6)])
1268
        self.assertEqual(expected_bytes, output.getvalue())
1269
1270
    def test_client_read_body_bytes_all(self):
1271
        # read_body_bytes should decode the body bytes from the wire into
1272
        # a response.
1273
        expected_bytes = "1234567"
1274
        server_bytes = "ok\n7\n1234567done\n"
1275
        input = StringIO(server_bytes)
1276
        output = StringIO()
1277
        medium = smart.SmartSimplePipesClientMedium(input, output)
1278
        protocol = smart.SmartClientRequestProtocolOne(medium.get_request())
1279
        protocol.call('foo')
1280
        protocol.read_response_tuple(True)
1281
        self.assertEqual(expected_bytes, protocol.read_body_bytes())
1282
1283
    def test_client_read_body_bytes_incremental(self):
1284
        # test reading a few bytes at a time from the body
1285
        # XXX: possibly we should test dribbling the bytes into the stringio
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
1286
        # to make the state machine work harder: however, as we use the
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1287
        # LengthPrefixedBodyDecoder that is already well tested - we can skip
1288
        # that.
1289
        expected_bytes = "1234567"
1290
        server_bytes = "ok\n7\n1234567done\n"
1291
        input = StringIO(server_bytes)
1292
        output = StringIO()
1293
        medium = smart.SmartSimplePipesClientMedium(input, output)
1294
        protocol = smart.SmartClientRequestProtocolOne(medium.get_request())
1295
        protocol.call('foo')
1296
        protocol.read_response_tuple(True)
1297
        self.assertEqual(expected_bytes[0:2], protocol.read_body_bytes(2))
1298
        self.assertEqual(expected_bytes[2:4], protocol.read_body_bytes(2))
1299
        self.assertEqual(expected_bytes[4:6], protocol.read_body_bytes(2))
1300
        self.assertEqual(expected_bytes[6], protocol.read_body_bytes())
1301
1302
    def test_client_cancel_read_body_does_not_eat_body_bytes(self):
1303
        # cancelling the expected body needs to finish the request, but not
1304
        # read any more bytes.
1305
        expected_bytes = "1234567"
1306
        server_bytes = "ok\n7\n1234567done\n"
1307
        input = StringIO(server_bytes)
1308
        output = StringIO()
1309
        medium = smart.SmartSimplePipesClientMedium(input, output)
1310
        protocol = smart.SmartClientRequestProtocolOne(medium.get_request())
1311
        protocol.call('foo')
1312
        protocol.read_response_tuple(True)
1313
        protocol.cancel_read_body()
1314
        self.assertEqual(3, input.tell())
1315
        self.assertRaises(errors.ReadingCompleted, protocol.read_body_bytes)
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1316
2018.2.15 by Andrew Bennetts
Remove SmartServerRequestProtocolOne.finished_reading attribute, replace with next_read_size method.
1317
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1318
class LengthPrefixedBodyDecoder(tests.TestCase):
1319
2018.2.4 by Robert Collins
separate out the client medium from the client encoding protocol for the smart server.
1320
    # XXX: TODO: make accept_reading_trailer invoke translate_response or 
1321
    # something similar to the ProtocolBase method.
1322
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1323
    def test_construct(self):
1324
        decoder = smart.LengthPrefixedBodyDecoder()
1325
        self.assertFalse(decoder.finished_reading)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1326
        self.assertEqual(6, decoder.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1327
        self.assertEqual('', decoder.read_pending_data())
1328
        self.assertEqual('', decoder.unused_data)
1329
1330
    def test_accept_bytes(self):
1331
        decoder = smart.LengthPrefixedBodyDecoder()
1332
        decoder.accept_bytes('')
1333
        self.assertFalse(decoder.finished_reading)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1334
        self.assertEqual(6, decoder.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1335
        self.assertEqual('', decoder.read_pending_data())
1336
        self.assertEqual('', decoder.unused_data)
1337
        decoder.accept_bytes('7')
1338
        self.assertFalse(decoder.finished_reading)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1339
        self.assertEqual(6, decoder.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1340
        self.assertEqual('', decoder.read_pending_data())
1341
        self.assertEqual('', decoder.unused_data)
1342
        decoder.accept_bytes('\na')
1343
        self.assertFalse(decoder.finished_reading)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1344
        self.assertEqual(11, decoder.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1345
        self.assertEqual('a', decoder.read_pending_data())
1346
        self.assertEqual('', decoder.unused_data)
1347
        decoder.accept_bytes('bcdefgd')
1348
        self.assertFalse(decoder.finished_reading)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1349
        self.assertEqual(4, decoder.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1350
        self.assertEqual('bcdefg', decoder.read_pending_data())
1351
        self.assertEqual('', decoder.unused_data)
1352
        decoder.accept_bytes('one')
1353
        self.assertFalse(decoder.finished_reading)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1354
        self.assertEqual(1, decoder.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1355
        self.assertEqual('', decoder.read_pending_data())
1356
        self.assertEqual('', decoder.unused_data)
1357
        decoder.accept_bytes('\nblarg')
1358
        self.assertTrue(decoder.finished_reading)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1359
        self.assertEqual(1, decoder.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1360
        self.assertEqual('', decoder.read_pending_data())
1361
        self.assertEqual('blarg', decoder.unused_data)
1362
        
1363
    def test_accept_bytes_all_at_once_with_excess(self):
1364
        decoder = smart.LengthPrefixedBodyDecoder()
1365
        decoder.accept_bytes('1\nadone\nunused')
1366
        self.assertTrue(decoder.finished_reading)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1367
        self.assertEqual(1, decoder.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1368
        self.assertEqual('a', decoder.read_pending_data())
1369
        self.assertEqual('unused', decoder.unused_data)
1370
1371
    def test_accept_bytes_exact_end_of_body(self):
1372
        decoder = smart.LengthPrefixedBodyDecoder()
1373
        decoder.accept_bytes('1\na')
1374
        self.assertFalse(decoder.finished_reading)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1375
        self.assertEqual(5, decoder.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1376
        self.assertEqual('a', decoder.read_pending_data())
1377
        self.assertEqual('', decoder.unused_data)
1378
        decoder.accept_bytes('done\n')
1379
        self.assertTrue(decoder.finished_reading)
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
1380
        self.assertEqual(1, decoder.next_read_size())
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1381
        self.assertEqual('', decoder.read_pending_data())
1382
        self.assertEqual('', decoder.unused_data)
1383
1384
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
1385
class FakeHTTPMedium(object):
1386
    def __init__(self):
1387
        self.written_request = None
1388
        self._current_request = None
2018.2.8 by Andrew Bennetts
Make HttpTransportBase.get_smart_client return self again.
1389
    def send_http_smart_request(self, bytes):
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
1390
        self.written_request = bytes
1391
        return None
1392
1393
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1394
class HTTPTunnellingSmokeTest(tests.TestCaseWithTransport):
1395
    
2018.2.7 by Andrew Bennetts
Implement _post on HttpTransport_urllib.
1396
    def _test_bulk_data(self, url_protocol):
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
1397
        # We should be able to send and receive bulk data in a single message.
1398
        # The 'readv' command in the smart protocol both sends and receives bulk
1399
        # data, so we use that.
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1400
        self.build_tree(['data-file'])
1401
        http_server = HTTPServerWithSmarts()
2018.2.7 by Andrew Bennetts
Implement _post on HttpTransport_urllib.
1402
        http_server._url_protocol = url_protocol
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1403
        http_server.setUp()
1404
        self.addCleanup(http_server.tearDown)
1405
1406
        http_transport = get_transport(http_server.get_url())
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
1407
1408
        medium = http_transport.get_smart_medium()
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
1409
        #remote_transport = RemoteTransport('fake_url', medium)
1410
        remote_transport = smart.SmartTransport('/', medium=medium)
1411
        self.assertEqual(
1412
            [(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1413
2018.2.7 by Andrew Bennetts
Implement _post on HttpTransport_urllib.
1414
    def test_bulk_data_pycurl(self):
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
1415
        try:
1416
            self._test_bulk_data('http+pycurl')
1417
        except errors.UnsupportedProtocol, e:
1418
            raise tests.TestSkipped(str(e))
2018.2.7 by Andrew Bennetts
Implement _post on HttpTransport_urllib.
1419
    
1420
    def test_bulk_data_urllib(self):
1421
        self._test_bulk_data('http+urllib')
1422
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
1423
    def test_smart_http_medium_request_accept_bytes(self):
1424
        medium = FakeHTTPMedium()
2018.2.8 by Andrew Bennetts
Make HttpTransportBase.get_smart_client return self again.
1425
        request = SmartClientHTTPMediumRequest(medium)
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
1426
        request.accept_bytes('abc')
1427
        request.accept_bytes('def')
1428
        self.assertEqual(None, medium.written_request)
1429
        request.finished_writing()
1430
        self.assertEqual('abcdef', medium.written_request)
1431
2018.2.7 by Andrew Bennetts
Implement _post on HttpTransport_urllib.
1432
    def _test_http_send_smart_request(self, url_protocol):
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
1433
        http_server = HTTPServerWithSmarts()
2018.2.7 by Andrew Bennetts
Implement _post on HttpTransport_urllib.
1434
        http_server._url_protocol = url_protocol
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
1435
        http_server.setUp()
1436
        self.addCleanup(http_server.tearDown)
1437
1438
        post_body = 'hello\n'
1439
        expected_reply_body = 'ok\x011\n'
1440
1441
        http_transport = get_transport(http_server.get_url())
1442
        medium = http_transport.get_smart_medium()
2018.2.8 by Andrew Bennetts
Make HttpTransportBase.get_smart_client return self again.
1443
        response = medium.send_http_smart_request(post_body)
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
1444
        reply_body = response.read()
1445
        self.assertEqual(expected_reply_body, reply_body)
1446
2018.2.7 by Andrew Bennetts
Implement _post on HttpTransport_urllib.
1447
    def test_http_send_smart_request_pycurl(self):
2018.2.26 by Andrew Bennetts
Changes prompted by j-a-meinel's review.
1448
        try:
1449
            self._test_http_send_smart_request('http+pycurl')
1450
        except errors.UnsupportedProtocol, e:
1451
            raise tests.TestSkipped(str(e))
2018.2.7 by Andrew Bennetts
Implement _post on HttpTransport_urllib.
1452
1453
    def test_http_send_smart_request_urllib(self):
1454
        self._test_http_send_smart_request('http+urllib')
1455
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1456
    def test_http_server_with_smarts(self):
1457
        http_server = HTTPServerWithSmarts()
1458
        http_server.setUp()
1459
        self.addCleanup(http_server.tearDown)
1460
1461
        post_body = 'hello\n'
1462
        expected_reply_body = 'ok\x011\n'
1463
1464
        smart_server_url = http_server.get_url() + '.bzr/smart'
1465
        reply = urllib2.urlopen(smart_server_url, post_body).read()
1466
1467
        self.assertEqual(expected_reply_body, reply)
1468
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
1469
    def test_smart_http_server_post_request_handler(self):
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
1470
        http_server = HTTPServerWithSmarts()
1471
        http_server.setUp()
1472
        self.addCleanup(http_server.tearDown)
1473
        httpd = http_server._get_httpd()
1474
1475
        socket = SampleSocket(
1476
            'POST /.bzr/smart HTTP/1.0\r\n'
1477
            # HTTP/1.0 posts must have a Content-Length.
1478
            'Content-Length: 6\r\n'
1479
            '\r\n'
1480
            'hello\n')
1481
        request_handler = SmartRequestHandler(
1482
            socket, ('localhost', 80), httpd)
1483
        response = socket.writefile.getvalue()
1484
        self.assertStartsWith(response, 'HTTP/1.0 200 ')
1485
        # This includes the end of the HTTP headers, and all the body.
1486
        expected_end_of_response = '\r\n\r\nok\x011\n'
1487
        self.assertEndsWith(response, expected_end_of_response)
1488
1489
1490
class SampleSocket(object):
1491
    """A socket-like object for use in testing the HTTP request handler."""
1492
    
1493
    def __init__(self, socket_read_content):
1494
        """Constructs a sample socket.
1495
1496
        :param socket_read_content: a byte sequence
1497
        """
1498
        # Use plain python StringIO so we can monkey-patch the close method to
1499
        # not discard the contents.
1500
        from StringIO import StringIO
1501
        self.readfile = StringIO(socket_read_content)
1502
        self.writefile = StringIO()
1503
        self.writefile.close = lambda: None
1504
        
1505
    def makefile(self, mode='r', bufsize=None):
1506
        if 'r' in mode:
1507
            return self.readfile
1508
        else:
1509
            return self.writefile
1510
1511
        
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
1512
# TODO: Client feature that does get_bundle and then installs that into a
1513
# branch; this can be used in place of the regular pull/fetch operation when
1514
# coming from a smart server.
1515
#
1516
# TODO: Eventually, want to do a 'branch' command by fetching the whole
1517
# history as one big bundle.  How?  
1518
#
1519
# The branch command does 'br_from.sprout', which tries to preserve the same
1520
# format.  We don't necessarily even want that.  
1521
#
1522
# It might be simpler to handle cmd_pull first, which does a simpler fetch()
1523
# operation from one branch into another.  It already has some code for
1524
# pulling from a bundle, which it does by trying to see if the destination is
1525
# a bundle file.  So it seems the logic for pull ought to be:
1526
# 
1527
#  - if it's a smart server, get a bundle from there and install that
1528
#  - if it's a bundle, install that
1529
#  - if it's a branch, pull from there
1530
#
1531
# Getting a bundle from a smart server is a bit different from reading a
1532
# bundle from a URL:
1533
#
1534
#  - we can reasonably remember the URL we last read from 
1535
#  - you can specify a revision number to pull, and we need to pass it across
1536
#    to the server as a limit on what will be requested
1537
#
1538
# TODO: Given a URL, determine whether it is a smart server or not (or perhaps
1539
# otherwise whether it's a bundle?)  Should this be a property or method of
1540
# the transport?  For the ssh protocol, we always know it's a smart server.
1541
# For http, we potentially need to probe.  But if we're explicitly given
1542
# bzr+http:// then we can skip that for now.