/brz/remove-bazaar

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