~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_smart.py

  • Committer: Robert Collins
  • Date: 2009-09-07 03:08:30 UTC
  • mto: This revision was merged to the branch mainline in revision 4690.
  • Revision ID: robertc@robertcollins.net-20090907030830-rf59kt28d550eauj
Milestones language tightning, internal consistency.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Tests for the smart wire/domain protocol.
 
18
 
 
19
This module contains tests for the domain-level smart requests and responses,
 
20
such as the 'Branch.lock_write' request. Many of these use specific disk
 
21
formats to exercise calls that only make sense for formats with specific
 
22
properties.
 
23
 
 
24
Tests for low-level protocol encoding are found in test_smart_transport.
 
25
"""
 
26
 
 
27
import bz2
 
28
from cStringIO import StringIO
 
29
import tarfile
 
30
 
 
31
from bzrlib import (
 
32
    bencode,
 
33
    bzrdir,
 
34
    errors,
 
35
    pack,
 
36
    smart,
 
37
    tests,
 
38
    urlutils,
 
39
    versionedfile,
 
40
    )
 
41
from bzrlib.branch import Branch, BranchReferenceFormat
 
42
import bzrlib.smart.branch
 
43
import bzrlib.smart.bzrdir, bzrlib.smart.bzrdir as smart_dir
 
44
import bzrlib.smart.packrepository
 
45
import bzrlib.smart.repository
 
46
from bzrlib.smart.request import (
 
47
    FailedSmartServerResponse,
 
48
    SmartServerRequest,
 
49
    SmartServerResponse,
 
50
    SuccessfulSmartServerResponse,
 
51
    )
 
52
from bzrlib.tests import (
 
53
    split_suite_by_re,
 
54
    )
 
55
from bzrlib.transport import chroot, get_transport
 
56
 
 
57
 
 
58
def load_tests(standard_tests, module, loader):
 
59
    """Multiply tests version and protocol consistency."""
 
60
    # FindRepository tests.
 
61
    bzrdir_mod = bzrlib.smart.bzrdir
 
62
    scenarios = [
 
63
        ("find_repository", {
 
64
            "_request_class":bzrdir_mod.SmartServerRequestFindRepositoryV1}),
 
65
        ("find_repositoryV2", {
 
66
            "_request_class":bzrdir_mod.SmartServerRequestFindRepositoryV2}),
 
67
        ("find_repositoryV3", {
 
68
            "_request_class":bzrdir_mod.SmartServerRequestFindRepositoryV3}),
 
69
        ]
 
70
    to_adapt, result = split_suite_by_re(standard_tests,
 
71
        "TestSmartServerRequestFindRepository")
 
72
    v2_only, v1_and_2 = split_suite_by_re(to_adapt,
 
73
        "_v2")
 
74
    tests.multiply_tests(v1_and_2, scenarios, result)
 
75
    # The first scenario is only applicable to v1 protocols, it is deleted
 
76
    # since.
 
77
    tests.multiply_tests(v2_only, scenarios[1:], result)
 
78
    return result
 
79
 
 
80
 
 
81
class TestCaseWithChrootedTransport(tests.TestCaseWithTransport):
 
82
 
 
83
    def setUp(self):
 
84
        tests.TestCaseWithTransport.setUp(self)
 
85
        self._chroot_server = None
 
86
 
 
87
    def get_transport(self, relpath=None):
 
88
        if self._chroot_server is None:
 
89
            backing_transport = tests.TestCaseWithTransport.get_transport(self)
 
90
            self._chroot_server = chroot.ChrootServer(backing_transport)
 
91
            self.start_server(self._chroot_server)
 
92
        t = get_transport(self._chroot_server.get_url())
 
93
        if relpath is not None:
 
94
            t = t.clone(relpath)
 
95
        return t
 
96
 
 
97
 
 
98
class TestCaseWithSmartMedium(tests.TestCaseWithTransport):
 
99
 
 
100
    def setUp(self):
 
101
        super(TestCaseWithSmartMedium, self).setUp()
 
102
        # We're allowed to set  the transport class here, so that we don't use
 
103
        # the default or a parameterized class, but rather use the
 
104
        # TestCaseWithTransport infrastructure to set up a smart server and
 
105
        # transport.
 
106
        self.transport_server = self.make_transport_server
 
107
 
 
108
    def make_transport_server(self):
 
109
        return smart.server.SmartTCPServer_for_testing('-' + self.id())
 
110
 
 
111
    def get_smart_medium(self):
 
112
        """Get a smart medium to use in tests."""
 
113
        return self.get_transport().get_smart_medium()
 
114
 
 
115
 
 
116
class TestByteStreamToStream(tests.TestCase):
 
117
 
 
118
    def test_repeated_substreams_same_kind_are_one_stream(self):
 
119
        # Make a stream - an iterable of bytestrings.
 
120
        stream = [('text', [versionedfile.FulltextContentFactory(('k1',), None,
 
121
            None, 'foo')]),('text', [
 
122
            versionedfile.FulltextContentFactory(('k2',), None, None, 'bar')])]
 
123
        fmt = bzrdir.format_registry.get('pack-0.92')().repository_format
 
124
        bytes = smart.repository._stream_to_byte_stream(stream, fmt)
 
125
        streams = []
 
126
        # Iterate the resulting iterable; checking that we get only one stream
 
127
        # out.
 
128
        fmt, stream = smart.repository._byte_stream_to_stream(bytes)
 
129
        for kind, substream in stream:
 
130
            streams.append((kind, list(substream)))
 
131
        self.assertLength(1, streams)
 
132
        self.assertLength(2, streams[0][1])
 
133
 
 
134
 
 
135
class TestSmartServerResponse(tests.TestCase):
 
136
 
 
137
    def test__eq__(self):
 
138
        self.assertEqual(SmartServerResponse(('ok', )),
 
139
            SmartServerResponse(('ok', )))
 
140
        self.assertEqual(SmartServerResponse(('ok', ), 'body'),
 
141
            SmartServerResponse(('ok', ), 'body'))
 
142
        self.assertNotEqual(SmartServerResponse(('ok', )),
 
143
            SmartServerResponse(('notok', )))
 
144
        self.assertNotEqual(SmartServerResponse(('ok', ), 'body'),
 
145
            SmartServerResponse(('ok', )))
 
146
        self.assertNotEqual(None,
 
147
            SmartServerResponse(('ok', )))
 
148
 
 
149
    def test__str__(self):
 
150
        """SmartServerResponses can be stringified."""
 
151
        self.assertEqual(
 
152
            "<SuccessfulSmartServerResponse args=('args',) body='body'>",
 
153
            str(SuccessfulSmartServerResponse(('args',), 'body')))
 
154
        self.assertEqual(
 
155
            "<FailedSmartServerResponse args=('args',) body='body'>",
 
156
            str(FailedSmartServerResponse(('args',), 'body')))
 
157
 
 
158
 
 
159
class TestSmartServerRequest(tests.TestCaseWithMemoryTransport):
 
160
 
 
161
    def test_translate_client_path(self):
 
162
        transport = self.get_transport()
 
163
        request = SmartServerRequest(transport, 'foo/')
 
164
        self.assertEqual('./', request.translate_client_path('foo/'))
 
165
        self.assertRaises(
 
166
            errors.InvalidURLJoin, request.translate_client_path, 'foo/..')
 
167
        self.assertRaises(
 
168
            errors.PathNotChild, request.translate_client_path, '/')
 
169
        self.assertRaises(
 
170
            errors.PathNotChild, request.translate_client_path, 'bar/')
 
171
        self.assertEqual('./baz', request.translate_client_path('foo/baz'))
 
172
 
 
173
    def test_transport_from_client_path(self):
 
174
        transport = self.get_transport()
 
175
        request = SmartServerRequest(transport, 'foo/')
 
176
        self.assertEqual(
 
177
            transport.base,
 
178
            request.transport_from_client_path('foo/').base)
 
179
 
 
180
 
 
181
class TestSmartServerBzrDirRequestCloningMetaDir(
 
182
    tests.TestCaseWithMemoryTransport):
 
183
    """Tests for BzrDir.cloning_metadir."""
 
184
 
 
185
    def test_cloning_metadir(self):
 
186
        """When there is a bzrdir present, the call succeeds."""
 
187
        backing = self.get_transport()
 
188
        dir = self.make_bzrdir('.')
 
189
        local_result = dir.cloning_metadir()
 
190
        request_class = smart_dir.SmartServerBzrDirRequestCloningMetaDir
 
191
        request = request_class(backing)
 
192
        expected = SuccessfulSmartServerResponse(
 
193
            (local_result.network_name(),
 
194
            local_result.repository_format.network_name(),
 
195
            ('branch', local_result.get_branch_format().network_name())))
 
196
        self.assertEqual(expected, request.execute('', 'False'))
 
197
 
 
198
    def test_cloning_metadir_reference(self):
 
199
        """The request fails when bzrdir contains a branch reference."""
 
200
        backing = self.get_transport()
 
201
        referenced_branch = self.make_branch('referenced')
 
202
        dir = self.make_bzrdir('.')
 
203
        local_result = dir.cloning_metadir()
 
204
        reference = BranchReferenceFormat().initialize(dir, referenced_branch)
 
205
        reference_url = BranchReferenceFormat().get_reference(dir)
 
206
        # The server shouldn't try to follow the branch reference, so it's fine
 
207
        # if the referenced branch isn't reachable.
 
208
        backing.rename('referenced', 'moved')
 
209
        request_class = smart_dir.SmartServerBzrDirRequestCloningMetaDir
 
210
        request = request_class(backing)
 
211
        expected = FailedSmartServerResponse(('BranchReference',))
 
212
        self.assertEqual(expected, request.execute('', 'False'))
 
213
 
 
214
 
 
215
class TestSmartServerRequestCreateRepository(tests.TestCaseWithMemoryTransport):
 
216
    """Tests for BzrDir.create_repository."""
 
217
 
 
218
    def test_makes_repository(self):
 
219
        """When there is a bzrdir present, the call succeeds."""
 
220
        backing = self.get_transport()
 
221
        self.make_bzrdir('.')
 
222
        request_class = bzrlib.smart.bzrdir.SmartServerRequestCreateRepository
 
223
        request = request_class(backing)
 
224
        reference_bzrdir_format = bzrdir.format_registry.get('pack-0.92')()
 
225
        reference_format = reference_bzrdir_format.repository_format
 
226
        network_name = reference_format.network_name()
 
227
        expected = SuccessfulSmartServerResponse(
 
228
            ('ok', 'no', 'no', 'no', network_name))
 
229
        self.assertEqual(expected, request.execute('', network_name, 'True'))
 
230
 
 
231
 
 
232
class TestSmartServerRequestFindRepository(tests.TestCaseWithMemoryTransport):
 
233
    """Tests for BzrDir.find_repository."""
 
234
 
 
235
    def test_no_repository(self):
 
236
        """When there is no repository to be found, ('norepository', ) is returned."""
 
237
        backing = self.get_transport()
 
238
        request = self._request_class(backing)
 
239
        self.make_bzrdir('.')
 
240
        self.assertEqual(SmartServerResponse(('norepository', )),
 
241
            request.execute(''))
 
242
 
 
243
    def test_nonshared_repository(self):
 
244
        # nonshared repositorys only allow 'find' to return a handle when the
 
245
        # path the repository is being searched on is the same as that that
 
246
        # the repository is at.
 
247
        backing = self.get_transport()
 
248
        request = self._request_class(backing)
 
249
        result = self._make_repository_and_result()
 
250
        self.assertEqual(result, request.execute(''))
 
251
        self.make_bzrdir('subdir')
 
252
        self.assertEqual(SmartServerResponse(('norepository', )),
 
253
            request.execute('subdir'))
 
254
 
 
255
    def _make_repository_and_result(self, shared=False, format=None):
 
256
        """Convenience function to setup a repository.
 
257
 
 
258
        :result: The SmartServerResponse to expect when opening it.
 
259
        """
 
260
        repo = self.make_repository('.', shared=shared, format=format)
 
261
        if repo.supports_rich_root():
 
262
            rich_root = 'yes'
 
263
        else:
 
264
            rich_root = 'no'
 
265
        if repo._format.supports_tree_reference:
 
266
            subtrees = 'yes'
 
267
        else:
 
268
            subtrees = 'no'
 
269
        if repo._format.supports_external_lookups:
 
270
            external = 'yes'
 
271
        else:
 
272
            external = 'no'
 
273
        if (smart.bzrdir.SmartServerRequestFindRepositoryV3 ==
 
274
            self._request_class):
 
275
            return SuccessfulSmartServerResponse(
 
276
                ('ok', '', rich_root, subtrees, external,
 
277
                 repo._format.network_name()))
 
278
        elif (smart.bzrdir.SmartServerRequestFindRepositoryV2 ==
 
279
            self._request_class):
 
280
            # All tests so far are on formats, and for non-external
 
281
            # repositories.
 
282
            return SuccessfulSmartServerResponse(
 
283
                ('ok', '', rich_root, subtrees, external))
 
284
        else:
 
285
            return SuccessfulSmartServerResponse(('ok', '', rich_root, subtrees))
 
286
 
 
287
    def test_shared_repository(self):
 
288
        """When there is a shared repository, we get 'ok', 'relpath-to-repo'."""
 
289
        backing = self.get_transport()
 
290
        request = self._request_class(backing)
 
291
        result = self._make_repository_and_result(shared=True)
 
292
        self.assertEqual(result, request.execute(''))
 
293
        self.make_bzrdir('subdir')
 
294
        result2 = SmartServerResponse(result.args[0:1] + ('..', ) + result.args[2:])
 
295
        self.assertEqual(result2,
 
296
            request.execute('subdir'))
 
297
        self.make_bzrdir('subdir/deeper')
 
298
        result3 = SmartServerResponse(result.args[0:1] + ('../..', ) + result.args[2:])
 
299
        self.assertEqual(result3,
 
300
            request.execute('subdir/deeper'))
 
301
 
 
302
    def test_rich_root_and_subtree_encoding(self):
 
303
        """Test for the format attributes for rich root and subtree support."""
 
304
        backing = self.get_transport()
 
305
        request = self._request_class(backing)
 
306
        result = self._make_repository_and_result(format='dirstate-with-subtree')
 
307
        # check the test will be valid
 
308
        self.assertEqual('yes', result.args[2])
 
309
        self.assertEqual('yes', result.args[3])
 
310
        self.assertEqual(result, request.execute(''))
 
311
 
 
312
    def test_supports_external_lookups_no_v2(self):
 
313
        """Test for the supports_external_lookups attribute."""
 
314
        backing = self.get_transport()
 
315
        request = self._request_class(backing)
 
316
        result = self._make_repository_and_result(format='dirstate-with-subtree')
 
317
        # check the test will be valid
 
318
        self.assertEqual('no', result.args[4])
 
319
        self.assertEqual(result, request.execute(''))
 
320
 
 
321
 
 
322
class TestSmartServerBzrDirRequestGetConfigFile(
 
323
    tests.TestCaseWithMemoryTransport):
 
324
    """Tests for BzrDir.get_config_file."""
 
325
 
 
326
    def test_present(self):
 
327
        backing = self.get_transport()
 
328
        dir = self.make_bzrdir('.')
 
329
        dir.get_config().set_default_stack_on("/")
 
330
        local_result = dir._get_config()._get_config_file().read()
 
331
        request_class = smart_dir.SmartServerBzrDirRequestConfigFile
 
332
        request = request_class(backing)
 
333
        expected = SuccessfulSmartServerResponse((), local_result)
 
334
        self.assertEqual(expected, request.execute(''))
 
335
 
 
336
    def test_missing(self):
 
337
        backing = self.get_transport()
 
338
        dir = self.make_bzrdir('.')
 
339
        request_class = smart_dir.SmartServerBzrDirRequestConfigFile
 
340
        request = request_class(backing)
 
341
        expected = SuccessfulSmartServerResponse((), '')
 
342
        self.assertEqual(expected, request.execute(''))
 
343
 
 
344
 
 
345
class TestSmartServerRequestInitializeBzrDir(tests.TestCaseWithMemoryTransport):
 
346
 
 
347
    def test_empty_dir(self):
 
348
        """Initializing an empty dir should succeed and do it."""
 
349
        backing = self.get_transport()
 
350
        request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
 
351
        self.assertEqual(SmartServerResponse(('ok', )),
 
352
            request.execute(''))
 
353
        made_dir = bzrdir.BzrDir.open_from_transport(backing)
 
354
        # no branch, tree or repository is expected with the current
 
355
        # default formart.
 
356
        self.assertRaises(errors.NoWorkingTree, made_dir.open_workingtree)
 
357
        self.assertRaises(errors.NotBranchError, made_dir.open_branch)
 
358
        self.assertRaises(errors.NoRepositoryPresent, made_dir.open_repository)
 
359
 
 
360
    def test_missing_dir(self):
 
361
        """Initializing a missing directory should fail like the bzrdir api."""
 
362
        backing = self.get_transport()
 
363
        request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
 
364
        self.assertRaises(errors.NoSuchFile,
 
365
            request.execute, 'subdir')
 
366
 
 
367
    def test_initialized_dir(self):
 
368
        """Initializing an extant bzrdir should fail like the bzrdir api."""
 
369
        backing = self.get_transport()
 
370
        request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
 
371
        self.make_bzrdir('subdir')
 
372
        self.assertRaises(errors.FileExists,
 
373
            request.execute, 'subdir')
 
374
 
 
375
 
 
376
class TestSmartServerRequestBzrDirInitializeEx(tests.TestCaseWithMemoryTransport):
 
377
    """Basic tests for BzrDir.initialize_ex_1.16 in the smart server.
 
378
 
 
379
    The main unit tests in test_bzrdir exercise the API comprehensively.
 
380
    """
 
381
 
 
382
    def test_empty_dir(self):
 
383
        """Initializing an empty dir should succeed and do it."""
 
384
        backing = self.get_transport()
 
385
        name = self.make_bzrdir('reference')._format.network_name()
 
386
        request = smart.bzrdir.SmartServerRequestBzrDirInitializeEx(backing)
 
387
        self.assertEqual(SmartServerResponse(('', '', '', '', '', '', name,
 
388
            'False', '', '', '')),
 
389
            request.execute(name, '', 'True', 'False', 'False', '', '', '', '',
 
390
            'False'))
 
391
        made_dir = bzrdir.BzrDir.open_from_transport(backing)
 
392
        # no branch, tree or repository is expected with the current
 
393
        # default format.
 
394
        self.assertRaises(errors.NoWorkingTree, made_dir.open_workingtree)
 
395
        self.assertRaises(errors.NotBranchError, made_dir.open_branch)
 
396
        self.assertRaises(errors.NoRepositoryPresent, made_dir.open_repository)
 
397
 
 
398
    def test_missing_dir(self):
 
399
        """Initializing a missing directory should fail like the bzrdir api."""
 
400
        backing = self.get_transport()
 
401
        name = self.make_bzrdir('reference')._format.network_name()
 
402
        request = smart.bzrdir.SmartServerRequestBzrDirInitializeEx(backing)
 
403
        self.assertRaises(errors.NoSuchFile, request.execute, name,
 
404
            'subdir/dir', 'False', 'False', 'False', '', '', '', '', 'False')
 
405
 
 
406
    def test_initialized_dir(self):
 
407
        """Initializing an extant directory should fail like the bzrdir api."""
 
408
        backing = self.get_transport()
 
409
        name = self.make_bzrdir('reference')._format.network_name()
 
410
        request = smart.bzrdir.SmartServerRequestBzrDirInitializeEx(backing)
 
411
        self.make_bzrdir('subdir')
 
412
        self.assertRaises(errors.FileExists, request.execute, name, 'subdir',
 
413
            'False', 'False', 'False', '', '', '', '', 'False')
 
414
 
 
415
 
 
416
class TestSmartServerRequestOpenBranch(TestCaseWithChrootedTransport):
 
417
 
 
418
    def test_no_branch(self):
 
419
        """When there is no branch, ('nobranch', ) is returned."""
 
420
        backing = self.get_transport()
 
421
        request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
 
422
        self.make_bzrdir('.')
 
423
        self.assertEqual(SmartServerResponse(('nobranch', )),
 
424
            request.execute(''))
 
425
 
 
426
    def test_branch(self):
 
427
        """When there is a branch, 'ok' is returned."""
 
428
        backing = self.get_transport()
 
429
        request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
 
430
        self.make_branch('.')
 
431
        self.assertEqual(SmartServerResponse(('ok', '')),
 
432
            request.execute(''))
 
433
 
 
434
    def test_branch_reference(self):
 
435
        """When there is a branch reference, the reference URL is returned."""
 
436
        backing = self.get_transport()
 
437
        request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
 
438
        branch = self.make_branch('branch')
 
439
        checkout = branch.create_checkout('reference',lightweight=True)
 
440
        reference_url = BranchReferenceFormat().get_reference(checkout.bzrdir)
 
441
        self.assertFileEqual(reference_url, 'reference/.bzr/branch/location')
 
442
        self.assertEqual(SmartServerResponse(('ok', reference_url)),
 
443
            request.execute('reference'))
 
444
 
 
445
 
 
446
class TestSmartServerRequestOpenBranchV2(TestCaseWithChrootedTransport):
 
447
 
 
448
    def test_no_branch(self):
 
449
        """When there is no branch, ('nobranch', ) is returned."""
 
450
        backing = self.get_transport()
 
451
        self.make_bzrdir('.')
 
452
        request = smart.bzrdir.SmartServerRequestOpenBranchV2(backing)
 
453
        self.assertEqual(SmartServerResponse(('nobranch', )),
 
454
            request.execute(''))
 
455
 
 
456
    def test_branch(self):
 
457
        """When there is a branch, 'ok' is returned."""
 
458
        backing = self.get_transport()
 
459
        expected = self.make_branch('.')._format.network_name()
 
460
        request = smart.bzrdir.SmartServerRequestOpenBranchV2(backing)
 
461
        self.assertEqual(SuccessfulSmartServerResponse(('branch', expected)),
 
462
            request.execute(''))
 
463
 
 
464
    def test_branch_reference(self):
 
465
        """When there is a branch reference, the reference URL is returned."""
 
466
        backing = self.get_transport()
 
467
        request = smart.bzrdir.SmartServerRequestOpenBranchV2(backing)
 
468
        branch = self.make_branch('branch')
 
469
        checkout = branch.create_checkout('reference',lightweight=True)
 
470
        reference_url = BranchReferenceFormat().get_reference(checkout.bzrdir)
 
471
        self.assertFileEqual(reference_url, 'reference/.bzr/branch/location')
 
472
        self.assertEqual(SuccessfulSmartServerResponse(('ref', reference_url)),
 
473
            request.execute('reference'))
 
474
 
 
475
    def test_stacked_branch(self):
 
476
        """Opening a stacked branch does not open the stacked-on branch."""
 
477
        trunk = self.make_branch('trunk')
 
478
        feature = self.make_branch('feature')
 
479
        feature.set_stacked_on_url(trunk.base)
 
480
        opened_branches = []
 
481
        Branch.hooks.install_named_hook('open', opened_branches.append, None)
 
482
        backing = self.get_transport()
 
483
        request = smart.bzrdir.SmartServerRequestOpenBranchV2(backing)
 
484
        request.setup_jail()
 
485
        try:
 
486
            response = request.execute('feature')
 
487
        finally:
 
488
            request.teardown_jail()
 
489
        expected_format = feature._format.network_name()
 
490
        self.assertEqual(
 
491
            SuccessfulSmartServerResponse(('branch', expected_format)),
 
492
            response)
 
493
        self.assertLength(1, opened_branches)
 
494
 
 
495
 
 
496
class TestSmartServerRequestRevisionHistory(tests.TestCaseWithMemoryTransport):
 
497
 
 
498
    def test_empty(self):
 
499
        """For an empty branch, the body is empty."""
 
500
        backing = self.get_transport()
 
501
        request = smart.branch.SmartServerRequestRevisionHistory(backing)
 
502
        self.make_branch('.')
 
503
        self.assertEqual(SmartServerResponse(('ok', ), ''),
 
504
            request.execute(''))
 
505
 
 
506
    def test_not_empty(self):
 
507
        """For a non-empty branch, the body is empty."""
 
508
        backing = self.get_transport()
 
509
        request = smart.branch.SmartServerRequestRevisionHistory(backing)
 
510
        tree = self.make_branch_and_memory_tree('.')
 
511
        tree.lock_write()
 
512
        tree.add('')
 
513
        r1 = tree.commit('1st commit')
 
514
        r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
 
515
        tree.unlock()
 
516
        self.assertEqual(
 
517
            SmartServerResponse(('ok', ), ('\x00'.join([r1, r2]))),
 
518
            request.execute(''))
 
519
 
 
520
 
 
521
class TestSmartServerBranchRequest(tests.TestCaseWithMemoryTransport):
 
522
 
 
523
    def test_no_branch(self):
 
524
        """When there is a bzrdir and no branch, NotBranchError is raised."""
 
525
        backing = self.get_transport()
 
526
        request = smart.branch.SmartServerBranchRequest(backing)
 
527
        self.make_bzrdir('.')
 
528
        self.assertRaises(errors.NotBranchError,
 
529
            request.execute, '')
 
530
 
 
531
    def test_branch_reference(self):
 
532
        """When there is a branch reference, NotBranchError is raised."""
 
533
        backing = self.get_transport()
 
534
        request = smart.branch.SmartServerBranchRequest(backing)
 
535
        branch = self.make_branch('branch')
 
536
        checkout = branch.create_checkout('reference',lightweight=True)
 
537
        self.assertRaises(errors.NotBranchError,
 
538
            request.execute, 'checkout')
 
539
 
 
540
 
 
541
class TestSmartServerBranchRequestLastRevisionInfo(tests.TestCaseWithMemoryTransport):
 
542
 
 
543
    def test_empty(self):
 
544
        """For an empty branch, the result is ('ok', '0', 'null:')."""
 
545
        backing = self.get_transport()
 
546
        request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
 
547
        self.make_branch('.')
 
548
        self.assertEqual(SmartServerResponse(('ok', '0', 'null:')),
 
549
            request.execute(''))
 
550
 
 
551
    def test_not_empty(self):
 
552
        """For a non-empty branch, the result is ('ok', 'revno', 'revid')."""
 
553
        backing = self.get_transport()
 
554
        request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
 
555
        tree = self.make_branch_and_memory_tree('.')
 
556
        tree.lock_write()
 
557
        tree.add('')
 
558
        rev_id_utf8 = u'\xc8'.encode('utf-8')
 
559
        r1 = tree.commit('1st commit')
 
560
        r2 = tree.commit('2nd commit', rev_id=rev_id_utf8)
 
561
        tree.unlock()
 
562
        self.assertEqual(
 
563
            SmartServerResponse(('ok', '2', rev_id_utf8)),
 
564
            request.execute(''))
 
565
 
 
566
 
 
567
class TestSmartServerBranchRequestGetConfigFile(tests.TestCaseWithMemoryTransport):
 
568
 
 
569
    def test_default(self):
 
570
        """With no file, we get empty content."""
 
571
        backing = self.get_transport()
 
572
        request = smart.branch.SmartServerBranchGetConfigFile(backing)
 
573
        branch = self.make_branch('.')
 
574
        # there should be no file by default
 
575
        content = ''
 
576
        self.assertEqual(SmartServerResponse(('ok', ), content),
 
577
            request.execute(''))
 
578
 
 
579
    def test_with_content(self):
 
580
        # SmartServerBranchGetConfigFile should return the content from
 
581
        # branch.control_files.get('branch.conf') for now - in the future it may
 
582
        # perform more complex processing.
 
583
        backing = self.get_transport()
 
584
        request = smart.branch.SmartServerBranchGetConfigFile(backing)
 
585
        branch = self.make_branch('.')
 
586
        branch._transport.put_bytes('branch.conf', 'foo bar baz')
 
587
        self.assertEqual(SmartServerResponse(('ok', ), 'foo bar baz'),
 
588
            request.execute(''))
 
589
 
 
590
 
 
591
class TestLockedBranch(tests.TestCaseWithMemoryTransport):
 
592
 
 
593
    def get_lock_tokens(self, branch):
 
594
        branch_token = branch.lock_write()
 
595
        repo_token = branch.repository.lock_write()
 
596
        branch.repository.unlock()
 
597
        return branch_token, repo_token
 
598
 
 
599
 
 
600
class TestSmartServerBranchRequestSetConfigOption(TestLockedBranch):
 
601
 
 
602
    def test_value_name(self):
 
603
        branch = self.make_branch('.')
 
604
        request = smart.branch.SmartServerBranchRequestSetConfigOption(
 
605
            branch.bzrdir.root_transport)
 
606
        branch_token, repo_token = self.get_lock_tokens(branch)
 
607
        config = branch._get_config()
 
608
        result = request.execute('', branch_token, repo_token, 'bar', 'foo',
 
609
            '')
 
610
        self.assertEqual(SuccessfulSmartServerResponse(()), result)
 
611
        self.assertEqual('bar', config.get_option('foo'))
 
612
        # Cleanup
 
613
        branch.unlock()
 
614
 
 
615
    def test_value_name_section(self):
 
616
        branch = self.make_branch('.')
 
617
        request = smart.branch.SmartServerBranchRequestSetConfigOption(
 
618
            branch.bzrdir.root_transport)
 
619
        branch_token, repo_token = self.get_lock_tokens(branch)
 
620
        config = branch._get_config()
 
621
        result = request.execute('', branch_token, repo_token, 'bar', 'foo',
 
622
            'gam')
 
623
        self.assertEqual(SuccessfulSmartServerResponse(()), result)
 
624
        self.assertEqual('bar', config.get_option('foo', 'gam'))
 
625
        # Cleanup
 
626
        branch.unlock()
 
627
 
 
628
 
 
629
class TestSmartServerBranchRequestSetTagsBytes(TestLockedBranch):
 
630
    # Only called when the branch format and tags match [yay factory
 
631
    # methods] so only need to test straight forward cases.
 
632
 
 
633
    def test_set_bytes(self):
 
634
        base_branch = self.make_branch('base')
 
635
        tag_bytes = base_branch._get_tags_bytes()
 
636
        # get_lock_tokens takes out a lock.
 
637
        branch_token, repo_token = self.get_lock_tokens(base_branch)
 
638
        request = smart.branch.SmartServerBranchSetTagsBytes(
 
639
            self.get_transport())
 
640
        response = request.execute('base', branch_token, repo_token)
 
641
        self.assertEqual(None, response)
 
642
        response = request.do_chunk(tag_bytes)
 
643
        self.assertEqual(None, response)
 
644
        response = request.do_end()
 
645
        self.assertEquals(
 
646
            SuccessfulSmartServerResponse(()), response)
 
647
        base_branch.unlock()
 
648
 
 
649
    def test_lock_failed(self):
 
650
        base_branch = self.make_branch('base')
 
651
        base_branch.lock_write()
 
652
        tag_bytes = base_branch._get_tags_bytes()
 
653
        request = smart.branch.SmartServerBranchSetTagsBytes(
 
654
            self.get_transport())
 
655
        self.assertRaises(errors.TokenMismatch, request.execute,
 
656
            'base', 'wrong token', 'wrong token')
 
657
        # The request handler will keep processing the message parts, so even
 
658
        # if the request fails immediately do_chunk and do_end are still
 
659
        # called.
 
660
        request.do_chunk(tag_bytes)
 
661
        request.do_end()
 
662
        base_branch.unlock()
 
663
 
 
664
 
 
665
 
 
666
class SetLastRevisionTestBase(TestLockedBranch):
 
667
    """Base test case for verbs that implement set_last_revision."""
 
668
 
 
669
    def setUp(self):
 
670
        tests.TestCaseWithMemoryTransport.setUp(self)
 
671
        backing_transport = self.get_transport()
 
672
        self.request = self.request_class(backing_transport)
 
673
        self.tree = self.make_branch_and_memory_tree('.')
 
674
 
 
675
    def lock_branch(self):
 
676
        return self.get_lock_tokens(self.tree.branch)
 
677
 
 
678
    def unlock_branch(self):
 
679
        self.tree.branch.unlock()
 
680
 
 
681
    def set_last_revision(self, revision_id, revno):
 
682
        branch_token, repo_token = self.lock_branch()
 
683
        response = self._set_last_revision(
 
684
            revision_id, revno, branch_token, repo_token)
 
685
        self.unlock_branch()
 
686
        return response
 
687
 
 
688
    def assertRequestSucceeds(self, revision_id, revno):
 
689
        response = self.set_last_revision(revision_id, revno)
 
690
        self.assertEqual(SuccessfulSmartServerResponse(('ok',)), response)
 
691
 
 
692
 
 
693
class TestSetLastRevisionVerbMixin(object):
 
694
    """Mixin test case for verbs that implement set_last_revision."""
 
695
 
 
696
    def test_set_null_to_null(self):
 
697
        """An empty branch can have its last revision set to 'null:'."""
 
698
        self.assertRequestSucceeds('null:', 0)
 
699
 
 
700
    def test_NoSuchRevision(self):
 
701
        """If the revision_id is not present, the verb returns NoSuchRevision.
 
702
        """
 
703
        revision_id = 'non-existent revision'
 
704
        self.assertEqual(
 
705
            FailedSmartServerResponse(('NoSuchRevision', revision_id)),
 
706
            self.set_last_revision(revision_id, 1))
 
707
 
 
708
    def make_tree_with_two_commits(self):
 
709
        self.tree.lock_write()
 
710
        self.tree.add('')
 
711
        rev_id_utf8 = u'\xc8'.encode('utf-8')
 
712
        r1 = self.tree.commit('1st commit', rev_id=rev_id_utf8)
 
713
        r2 = self.tree.commit('2nd commit', rev_id='rev-2')
 
714
        self.tree.unlock()
 
715
 
 
716
    def test_branch_last_revision_info_is_updated(self):
 
717
        """A branch's tip can be set to a revision that is present in its
 
718
        repository.
 
719
        """
 
720
        # Make a branch with an empty revision history, but two revisions in
 
721
        # its repository.
 
722
        self.make_tree_with_two_commits()
 
723
        rev_id_utf8 = u'\xc8'.encode('utf-8')
 
724
        self.tree.branch.set_revision_history([])
 
725
        self.assertEqual(
 
726
            (0, 'null:'), self.tree.branch.last_revision_info())
 
727
        # We can update the branch to a revision that is present in the
 
728
        # repository.
 
729
        self.assertRequestSucceeds(rev_id_utf8, 1)
 
730
        self.assertEqual(
 
731
            (1, rev_id_utf8), self.tree.branch.last_revision_info())
 
732
 
 
733
    def test_branch_last_revision_info_rewind(self):
 
734
        """A branch's tip can be set to a revision that is an ancestor of the
 
735
        current tip.
 
736
        """
 
737
        self.make_tree_with_two_commits()
 
738
        rev_id_utf8 = u'\xc8'.encode('utf-8')
 
739
        self.assertEqual(
 
740
            (2, 'rev-2'), self.tree.branch.last_revision_info())
 
741
        self.assertRequestSucceeds(rev_id_utf8, 1)
 
742
        self.assertEqual(
 
743
            (1, rev_id_utf8), self.tree.branch.last_revision_info())
 
744
 
 
745
    def test_TipChangeRejected(self):
 
746
        """If a pre_change_branch_tip hook raises TipChangeRejected, the verb
 
747
        returns TipChangeRejected.
 
748
        """
 
749
        rejection_message = u'rejection message\N{INTERROBANG}'
 
750
        def hook_that_rejects(params):
 
751
            raise errors.TipChangeRejected(rejection_message)
 
752
        Branch.hooks.install_named_hook(
 
753
            'pre_change_branch_tip', hook_that_rejects, None)
 
754
        self.assertEqual(
 
755
            FailedSmartServerResponse(
 
756
                ('TipChangeRejected', rejection_message.encode('utf-8'))),
 
757
            self.set_last_revision('null:', 0))
 
758
 
 
759
 
 
760
class TestSmartServerBranchRequestSetLastRevision(
 
761
        SetLastRevisionTestBase, TestSetLastRevisionVerbMixin):
 
762
    """Tests for Branch.set_last_revision verb."""
 
763
 
 
764
    request_class = smart.branch.SmartServerBranchRequestSetLastRevision
 
765
 
 
766
    def _set_last_revision(self, revision_id, revno, branch_token, repo_token):
 
767
        return self.request.execute(
 
768
            '', branch_token, repo_token, revision_id)
 
769
 
 
770
 
 
771
class TestSmartServerBranchRequestSetLastRevisionInfo(
 
772
        SetLastRevisionTestBase, TestSetLastRevisionVerbMixin):
 
773
    """Tests for Branch.set_last_revision_info verb."""
 
774
 
 
775
    request_class = smart.branch.SmartServerBranchRequestSetLastRevisionInfo
 
776
 
 
777
    def _set_last_revision(self, revision_id, revno, branch_token, repo_token):
 
778
        return self.request.execute(
 
779
            '', branch_token, repo_token, revno, revision_id)
 
780
 
 
781
    def test_NoSuchRevision(self):
 
782
        """Branch.set_last_revision_info does not have to return
 
783
        NoSuchRevision if the revision_id is absent.
 
784
        """
 
785
        raise tests.TestNotApplicable()
 
786
 
 
787
 
 
788
class TestSmartServerBranchRequestSetLastRevisionEx(
 
789
        SetLastRevisionTestBase, TestSetLastRevisionVerbMixin):
 
790
    """Tests for Branch.set_last_revision_ex verb."""
 
791
 
 
792
    request_class = smart.branch.SmartServerBranchRequestSetLastRevisionEx
 
793
 
 
794
    def _set_last_revision(self, revision_id, revno, branch_token, repo_token):
 
795
        return self.request.execute(
 
796
            '', branch_token, repo_token, revision_id, 0, 0)
 
797
 
 
798
    def assertRequestSucceeds(self, revision_id, revno):
 
799
        response = self.set_last_revision(revision_id, revno)
 
800
        self.assertEqual(
 
801
            SuccessfulSmartServerResponse(('ok', revno, revision_id)),
 
802
            response)
 
803
 
 
804
    def test_branch_last_revision_info_rewind(self):
 
805
        """A branch's tip can be set to a revision that is an ancestor of the
 
806
        current tip, but only if allow_overwrite_descendant is passed.
 
807
        """
 
808
        self.make_tree_with_two_commits()
 
809
        rev_id_utf8 = u'\xc8'.encode('utf-8')
 
810
        self.assertEqual(
 
811
            (2, 'rev-2'), self.tree.branch.last_revision_info())
 
812
        # If allow_overwrite_descendant flag is 0, then trying to set the tip
 
813
        # to an older revision ID has no effect.
 
814
        branch_token, repo_token = self.lock_branch()
 
815
        response = self.request.execute(
 
816
            '', branch_token, repo_token, rev_id_utf8, 0, 0)
 
817
        self.assertEqual(
 
818
            SuccessfulSmartServerResponse(('ok', 2, 'rev-2')),
 
819
            response)
 
820
        self.assertEqual(
 
821
            (2, 'rev-2'), self.tree.branch.last_revision_info())
 
822
 
 
823
        # If allow_overwrite_descendant flag is 1, then setting the tip to an
 
824
        # ancestor works.
 
825
        response = self.request.execute(
 
826
            '', branch_token, repo_token, rev_id_utf8, 0, 1)
 
827
        self.assertEqual(
 
828
            SuccessfulSmartServerResponse(('ok', 1, rev_id_utf8)),
 
829
            response)
 
830
        self.unlock_branch()
 
831
        self.assertEqual(
 
832
            (1, rev_id_utf8), self.tree.branch.last_revision_info())
 
833
 
 
834
    def make_branch_with_divergent_history(self):
 
835
        """Make a branch with divergent history in its repo.
 
836
 
 
837
        The branch's tip will be 'child-2', and the repo will also contain
 
838
        'child-1', which diverges from a common base revision.
 
839
        """
 
840
        self.tree.lock_write()
 
841
        self.tree.add('')
 
842
        r1 = self.tree.commit('1st commit')
 
843
        revno_1, revid_1 = self.tree.branch.last_revision_info()
 
844
        r2 = self.tree.commit('2nd commit', rev_id='child-1')
 
845
        # Undo the second commit
 
846
        self.tree.branch.set_last_revision_info(revno_1, revid_1)
 
847
        self.tree.set_parent_ids([revid_1])
 
848
        # Make a new second commit, child-2.  child-2 has diverged from
 
849
        # child-1.
 
850
        new_r2 = self.tree.commit('2nd commit', rev_id='child-2')
 
851
        self.tree.unlock()
 
852
 
 
853
    def test_not_allow_diverged(self):
 
854
        """If allow_diverged is not passed, then setting a divergent history
 
855
        returns a Diverged error.
 
856
        """
 
857
        self.make_branch_with_divergent_history()
 
858
        self.assertEqual(
 
859
            FailedSmartServerResponse(('Diverged',)),
 
860
            self.set_last_revision('child-1', 2))
 
861
        # The branch tip was not changed.
 
862
        self.assertEqual('child-2', self.tree.branch.last_revision())
 
863
 
 
864
    def test_allow_diverged(self):
 
865
        """If allow_diverged is passed, then setting a divergent history
 
866
        succeeds.
 
867
        """
 
868
        self.make_branch_with_divergent_history()
 
869
        branch_token, repo_token = self.lock_branch()
 
870
        response = self.request.execute(
 
871
            '', branch_token, repo_token, 'child-1', 1, 0)
 
872
        self.assertEqual(
 
873
            SuccessfulSmartServerResponse(('ok', 2, 'child-1')),
 
874
            response)
 
875
        self.unlock_branch()
 
876
        # The branch tip was changed.
 
877
        self.assertEqual('child-1', self.tree.branch.last_revision())
 
878
 
 
879
 
 
880
class TestSmartServerBranchRequestGetParent(tests.TestCaseWithMemoryTransport):
 
881
 
 
882
    def test_get_parent_none(self):
 
883
        base_branch = self.make_branch('base')
 
884
        request = smart.branch.SmartServerBranchGetParent(self.get_transport())
 
885
        response = request.execute('base')
 
886
        self.assertEquals(
 
887
            SuccessfulSmartServerResponse(('',)), response)
 
888
 
 
889
    def test_get_parent_something(self):
 
890
        base_branch = self.make_branch('base')
 
891
        base_branch.set_parent(self.get_url('foo'))
 
892
        request = smart.branch.SmartServerBranchGetParent(self.get_transport())
 
893
        response = request.execute('base')
 
894
        self.assertEquals(
 
895
            SuccessfulSmartServerResponse(("../foo",)),
 
896
            response)
 
897
 
 
898
 
 
899
class TestSmartServerBranchRequestSetParent(tests.TestCaseWithMemoryTransport):
 
900
 
 
901
    def test_set_parent_none(self):
 
902
        branch = self.make_branch('base', format="1.9")
 
903
        branch.lock_write()
 
904
        branch._set_parent_location('foo')
 
905
        branch.unlock()
 
906
        request = smart.branch.SmartServerBranchRequestSetParentLocation(
 
907
            self.get_transport())
 
908
        branch_token = branch.lock_write()
 
909
        repo_token = branch.repository.lock_write()
 
910
        try:
 
911
            response = request.execute('base', branch_token, repo_token, '')
 
912
        finally:
 
913
            branch.repository.unlock()
 
914
            branch.unlock()
 
915
        self.assertEqual(SuccessfulSmartServerResponse(()), response)
 
916
        self.assertEqual(None, branch.get_parent())
 
917
 
 
918
    def test_set_parent_something(self):
 
919
        branch = self.make_branch('base', format="1.9")
 
920
        request = smart.branch.SmartServerBranchRequestSetParentLocation(
 
921
            self.get_transport())
 
922
        branch_token = branch.lock_write()
 
923
        repo_token = branch.repository.lock_write()
 
924
        try:
 
925
            response = request.execute('base', branch_token, repo_token,
 
926
            'http://bar/')
 
927
        finally:
 
928
            branch.repository.unlock()
 
929
            branch.unlock()
 
930
        self.assertEqual(SuccessfulSmartServerResponse(()), response)
 
931
        self.assertEqual('http://bar/', branch.get_parent())
 
932
 
 
933
 
 
934
class TestSmartServerBranchRequestGetTagsBytes(tests.TestCaseWithMemoryTransport):
 
935
    # Only called when the branch format and tags match [yay factory
 
936
    # methods] so only need to test straight forward cases.
 
937
 
 
938
    def test_get_bytes(self):
 
939
        base_branch = self.make_branch('base')
 
940
        request = smart.branch.SmartServerBranchGetTagsBytes(
 
941
            self.get_transport())
 
942
        response = request.execute('base')
 
943
        self.assertEquals(
 
944
            SuccessfulSmartServerResponse(('',)), response)
 
945
 
 
946
 
 
947
class TestSmartServerBranchRequestGetStackedOnURL(tests.TestCaseWithMemoryTransport):
 
948
 
 
949
    def test_get_stacked_on_url(self):
 
950
        base_branch = self.make_branch('base', format='1.6')
 
951
        stacked_branch = self.make_branch('stacked', format='1.6')
 
952
        # typically should be relative
 
953
        stacked_branch.set_stacked_on_url('../base')
 
954
        request = smart.branch.SmartServerBranchRequestGetStackedOnURL(
 
955
            self.get_transport())
 
956
        response = request.execute('stacked')
 
957
        self.assertEquals(
 
958
            SmartServerResponse(('ok', '../base')),
 
959
            response)
 
960
 
 
961
 
 
962
class TestSmartServerBranchRequestLockWrite(tests.TestCaseWithMemoryTransport):
 
963
 
 
964
    def setUp(self):
 
965
        tests.TestCaseWithMemoryTransport.setUp(self)
 
966
 
 
967
    def test_lock_write_on_unlocked_branch(self):
 
968
        backing = self.get_transport()
 
969
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
970
        branch = self.make_branch('.', format='knit')
 
971
        repository = branch.repository
 
972
        response = request.execute('')
 
973
        branch_nonce = branch.control_files._lock.peek().get('nonce')
 
974
        repository_nonce = repository.control_files._lock.peek().get('nonce')
 
975
        self.assertEqual(
 
976
            SmartServerResponse(('ok', branch_nonce, repository_nonce)),
 
977
            response)
 
978
        # The branch (and associated repository) is now locked.  Verify that
 
979
        # with a new branch object.
 
980
        new_branch = repository.bzrdir.open_branch()
 
981
        self.assertRaises(errors.LockContention, new_branch.lock_write)
 
982
        # Cleanup
 
983
        request = smart.branch.SmartServerBranchRequestUnlock(backing)
 
984
        response = request.execute('', branch_nonce, repository_nonce)
 
985
 
 
986
    def test_lock_write_on_locked_branch(self):
 
987
        backing = self.get_transport()
 
988
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
989
        branch = self.make_branch('.')
 
990
        branch_token = branch.lock_write()
 
991
        branch.leave_lock_in_place()
 
992
        branch.unlock()
 
993
        response = request.execute('')
 
994
        self.assertEqual(
 
995
            SmartServerResponse(('LockContention',)), response)
 
996
        # Cleanup
 
997
        branch.lock_write(branch_token)
 
998
        branch.dont_leave_lock_in_place()
 
999
        branch.unlock()
 
1000
 
 
1001
    def test_lock_write_with_tokens_on_locked_branch(self):
 
1002
        backing = self.get_transport()
 
1003
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
1004
        branch = self.make_branch('.', format='knit')
 
1005
        branch_token = branch.lock_write()
 
1006
        repo_token = branch.repository.lock_write()
 
1007
        branch.repository.unlock()
 
1008
        branch.leave_lock_in_place()
 
1009
        branch.repository.leave_lock_in_place()
 
1010
        branch.unlock()
 
1011
        response = request.execute('',
 
1012
                                   branch_token, repo_token)
 
1013
        self.assertEqual(
 
1014
            SmartServerResponse(('ok', branch_token, repo_token)), response)
 
1015
        # Cleanup
 
1016
        branch.repository.lock_write(repo_token)
 
1017
        branch.repository.dont_leave_lock_in_place()
 
1018
        branch.repository.unlock()
 
1019
        branch.lock_write(branch_token)
 
1020
        branch.dont_leave_lock_in_place()
 
1021
        branch.unlock()
 
1022
 
 
1023
    def test_lock_write_with_mismatched_tokens_on_locked_branch(self):
 
1024
        backing = self.get_transport()
 
1025
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
1026
        branch = self.make_branch('.', format='knit')
 
1027
        branch_token = branch.lock_write()
 
1028
        repo_token = branch.repository.lock_write()
 
1029
        branch.repository.unlock()
 
1030
        branch.leave_lock_in_place()
 
1031
        branch.repository.leave_lock_in_place()
 
1032
        branch.unlock()
 
1033
        response = request.execute('',
 
1034
                                   branch_token+'xxx', repo_token)
 
1035
        self.assertEqual(
 
1036
            SmartServerResponse(('TokenMismatch',)), response)
 
1037
        # Cleanup
 
1038
        branch.repository.lock_write(repo_token)
 
1039
        branch.repository.dont_leave_lock_in_place()
 
1040
        branch.repository.unlock()
 
1041
        branch.lock_write(branch_token)
 
1042
        branch.dont_leave_lock_in_place()
 
1043
        branch.unlock()
 
1044
 
 
1045
    def test_lock_write_on_locked_repo(self):
 
1046
        backing = self.get_transport()
 
1047
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
1048
        branch = self.make_branch('.', format='knit')
 
1049
        repo = branch.repository
 
1050
        repo_token = repo.lock_write()
 
1051
        repo.leave_lock_in_place()
 
1052
        repo.unlock()
 
1053
        response = request.execute('')
 
1054
        self.assertEqual(
 
1055
            SmartServerResponse(('LockContention',)), response)
 
1056
        # Cleanup
 
1057
        repo.lock_write(repo_token)
 
1058
        repo.dont_leave_lock_in_place()
 
1059
        repo.unlock()
 
1060
 
 
1061
    def test_lock_write_on_readonly_transport(self):
 
1062
        backing = self.get_readonly_transport()
 
1063
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
1064
        branch = self.make_branch('.')
 
1065
        root = self.get_transport().clone('/')
 
1066
        path = urlutils.relative_url(root.base, self.get_transport().base)
 
1067
        response = request.execute(path)
 
1068
        error_name, lock_str, why_str = response.args
 
1069
        self.assertFalse(response.is_successful())
 
1070
        self.assertEqual('LockFailed', error_name)
 
1071
 
 
1072
 
 
1073
class TestSmartServerBranchRequestUnlock(tests.TestCaseWithMemoryTransport):
 
1074
 
 
1075
    def setUp(self):
 
1076
        tests.TestCaseWithMemoryTransport.setUp(self)
 
1077
 
 
1078
    def test_unlock_on_locked_branch_and_repo(self):
 
1079
        backing = self.get_transport()
 
1080
        request = smart.branch.SmartServerBranchRequestUnlock(backing)
 
1081
        branch = self.make_branch('.', format='knit')
 
1082
        # Lock the branch
 
1083
        branch_token = branch.lock_write()
 
1084
        repo_token = branch.repository.lock_write()
 
1085
        branch.repository.unlock()
 
1086
        # Unlock the branch (and repo) object, leaving the physical locks
 
1087
        # in place.
 
1088
        branch.leave_lock_in_place()
 
1089
        branch.repository.leave_lock_in_place()
 
1090
        branch.unlock()
 
1091
        response = request.execute('',
 
1092
                                   branch_token, repo_token)
 
1093
        self.assertEqual(
 
1094
            SmartServerResponse(('ok',)), response)
 
1095
        # The branch is now unlocked.  Verify that with a new branch
 
1096
        # object.
 
1097
        new_branch = branch.bzrdir.open_branch()
 
1098
        new_branch.lock_write()
 
1099
        new_branch.unlock()
 
1100
 
 
1101
    def test_unlock_on_unlocked_branch_unlocked_repo(self):
 
1102
        backing = self.get_transport()
 
1103
        request = smart.branch.SmartServerBranchRequestUnlock(backing)
 
1104
        branch = self.make_branch('.', format='knit')
 
1105
        response = request.execute(
 
1106
            '', 'branch token', 'repo token')
 
1107
        self.assertEqual(
 
1108
            SmartServerResponse(('TokenMismatch',)), response)
 
1109
 
 
1110
    def test_unlock_on_unlocked_branch_locked_repo(self):
 
1111
        backing = self.get_transport()
 
1112
        request = smart.branch.SmartServerBranchRequestUnlock(backing)
 
1113
        branch = self.make_branch('.', format='knit')
 
1114
        # Lock the repository.
 
1115
        repo_token = branch.repository.lock_write()
 
1116
        branch.repository.leave_lock_in_place()
 
1117
        branch.repository.unlock()
 
1118
        # Issue branch lock_write request on the unlocked branch (with locked
 
1119
        # repo).
 
1120
        response = request.execute(
 
1121
            '', 'branch token', repo_token)
 
1122
        self.assertEqual(
 
1123
            SmartServerResponse(('TokenMismatch',)), response)
 
1124
        # Cleanup
 
1125
        branch.repository.lock_write(repo_token)
 
1126
        branch.repository.dont_leave_lock_in_place()
 
1127
        branch.repository.unlock()
 
1128
 
 
1129
 
 
1130
class TestSmartServerRepositoryRequest(tests.TestCaseWithMemoryTransport):
 
1131
 
 
1132
    def test_no_repository(self):
 
1133
        """Raise NoRepositoryPresent when there is a bzrdir and no repo."""
 
1134
        # we test this using a shared repository above the named path,
 
1135
        # thus checking the right search logic is used - that is, that
 
1136
        # its the exact path being looked at and the server is not
 
1137
        # searching.
 
1138
        backing = self.get_transport()
 
1139
        request = smart.repository.SmartServerRepositoryRequest(backing)
 
1140
        self.make_repository('.', shared=True)
 
1141
        self.make_bzrdir('subdir')
 
1142
        self.assertRaises(errors.NoRepositoryPresent,
 
1143
            request.execute, 'subdir')
 
1144
 
 
1145
 
 
1146
class TestSmartServerRepositoryGetParentMap(tests.TestCaseWithMemoryTransport):
 
1147
 
 
1148
    def test_trivial_bzipped(self):
 
1149
        # This tests that the wire encoding is actually bzipped
 
1150
        backing = self.get_transport()
 
1151
        request = smart.repository.SmartServerRepositoryGetParentMap(backing)
 
1152
        tree = self.make_branch_and_memory_tree('.')
 
1153
 
 
1154
        self.assertEqual(None,
 
1155
            request.execute('', 'missing-id'))
 
1156
        # Note that it returns a body that is bzipped.
 
1157
        self.assertEqual(
 
1158
            SuccessfulSmartServerResponse(('ok', ), bz2.compress('')),
 
1159
            request.do_body('\n\n0\n'))
 
1160
 
 
1161
    def test_trivial_include_missing(self):
 
1162
        backing = self.get_transport()
 
1163
        request = smart.repository.SmartServerRepositoryGetParentMap(backing)
 
1164
        tree = self.make_branch_and_memory_tree('.')
 
1165
 
 
1166
        self.assertEqual(None,
 
1167
            request.execute('', 'missing-id', 'include-missing:'))
 
1168
        self.assertEqual(
 
1169
            SuccessfulSmartServerResponse(('ok', ),
 
1170
                bz2.compress('missing:missing-id')),
 
1171
            request.do_body('\n\n0\n'))
 
1172
 
 
1173
 
 
1174
class TestSmartServerRepositoryGetRevisionGraph(tests.TestCaseWithMemoryTransport):
 
1175
 
 
1176
    def test_none_argument(self):
 
1177
        backing = self.get_transport()
 
1178
        request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
 
1179
        tree = self.make_branch_and_memory_tree('.')
 
1180
        tree.lock_write()
 
1181
        tree.add('')
 
1182
        r1 = tree.commit('1st commit')
 
1183
        r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
 
1184
        tree.unlock()
 
1185
 
 
1186
        # the lines of revision_id->revision_parent_list has no guaranteed
 
1187
        # order coming out of a dict, so sort both our test and response
 
1188
        lines = sorted([' '.join([r2, r1]), r1])
 
1189
        response = request.execute('', '')
 
1190
        response.body = '\n'.join(sorted(response.body.split('\n')))
 
1191
 
 
1192
        self.assertEqual(
 
1193
            SmartServerResponse(('ok', ), '\n'.join(lines)), response)
 
1194
 
 
1195
    def test_specific_revision_argument(self):
 
1196
        backing = self.get_transport()
 
1197
        request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
 
1198
        tree = self.make_branch_and_memory_tree('.')
 
1199
        tree.lock_write()
 
1200
        tree.add('')
 
1201
        rev_id_utf8 = u'\xc9'.encode('utf-8')
 
1202
        r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
 
1203
        r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
 
1204
        tree.unlock()
 
1205
 
 
1206
        self.assertEqual(SmartServerResponse(('ok', ), rev_id_utf8),
 
1207
            request.execute('', rev_id_utf8))
 
1208
 
 
1209
    def test_no_such_revision(self):
 
1210
        backing = self.get_transport()
 
1211
        request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
 
1212
        tree = self.make_branch_and_memory_tree('.')
 
1213
        tree.lock_write()
 
1214
        tree.add('')
 
1215
        r1 = tree.commit('1st commit')
 
1216
        tree.unlock()
 
1217
 
 
1218
        # Note that it still returns body (of zero bytes).
 
1219
        self.assertEqual(
 
1220
            SmartServerResponse(('nosuchrevision', 'missingrevision', ), ''),
 
1221
            request.execute('', 'missingrevision'))
 
1222
 
 
1223
 
 
1224
class TestSmartServerRepositoryGetRevIdForRevno(tests.TestCaseWithMemoryTransport):
 
1225
 
 
1226
    def test_revno_found(self):
 
1227
        backing = self.get_transport()
 
1228
        request = smart.repository.SmartServerRepositoryGetRevIdForRevno(backing)
 
1229
        tree = self.make_branch_and_memory_tree('.')
 
1230
        tree.lock_write()
 
1231
        tree.add('')
 
1232
        rev1_id_utf8 = u'\xc8'.encode('utf-8')
 
1233
        rev2_id_utf8 = u'\xc9'.encode('utf-8')
 
1234
        tree.commit('1st commit', rev_id=rev1_id_utf8)
 
1235
        tree.commit('2nd commit', rev_id=rev2_id_utf8)
 
1236
        tree.unlock()
 
1237
 
 
1238
        self.assertEqual(SmartServerResponse(('ok', rev1_id_utf8)),
 
1239
            request.execute('', 1, (2, rev2_id_utf8)))
 
1240
 
 
1241
    def test_known_revid_missing(self):
 
1242
        backing = self.get_transport()
 
1243
        request = smart.repository.SmartServerRepositoryGetRevIdForRevno(backing)
 
1244
        repo = self.make_repository('.')
 
1245
        self.assertEqual(
 
1246
            FailedSmartServerResponse(('nosuchrevision', 'ghost')),
 
1247
            request.execute('', 1, (2, 'ghost')))
 
1248
 
 
1249
    def test_history_incomplete(self):
 
1250
        backing = self.get_transport()
 
1251
        request = smart.repository.SmartServerRepositoryGetRevIdForRevno(backing)
 
1252
        parent = self.make_branch_and_memory_tree('parent', format='1.9')
 
1253
        parent.lock_write()
 
1254
        parent.add([''], ['TREE_ROOT'])
 
1255
        r1 = parent.commit(message='first commit')
 
1256
        r2 = parent.commit(message='second commit')
 
1257
        parent.unlock()
 
1258
        local = self.make_branch_and_memory_tree('local', format='1.9')
 
1259
        local.branch.pull(parent.branch)
 
1260
        local.set_parent_ids([r2])
 
1261
        r3 = local.commit(message='local commit')
 
1262
        local.branch.create_clone_on_transport(
 
1263
            self.get_transport('stacked'), stacked_on=self.get_url('parent'))
 
1264
        self.assertEqual(
 
1265
            SmartServerResponse(('history-incomplete', 2, r2)),
 
1266
            request.execute('stacked', 1, (3, r3)))
 
1267
 
 
1268
 
 
1269
class TestSmartServerRepositoryGetStream(tests.TestCaseWithMemoryTransport):
 
1270
 
 
1271
    def make_two_commit_repo(self):
 
1272
        tree = self.make_branch_and_memory_tree('.')
 
1273
        tree.lock_write()
 
1274
        tree.add('')
 
1275
        r1 = tree.commit('1st commit')
 
1276
        r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
 
1277
        tree.unlock()
 
1278
        repo = tree.branch.repository
 
1279
        return repo, r1, r2
 
1280
 
 
1281
    def test_ancestry_of(self):
 
1282
        """The search argument may be a 'ancestry-of' some heads'."""
 
1283
        backing = self.get_transport()
 
1284
        request = smart.repository.SmartServerRepositoryGetStream(backing)
 
1285
        repo, r1, r2 = self.make_two_commit_repo()
 
1286
        fetch_spec = ['ancestry-of', r2]
 
1287
        lines = '\n'.join(fetch_spec)
 
1288
        request.execute('', repo._format.network_name())
 
1289
        response = request.do_body(lines)
 
1290
        self.assertEqual(('ok',), response.args)
 
1291
        stream_bytes = ''.join(response.body_stream)
 
1292
        self.assertStartsWith(stream_bytes, 'Bazaar pack format 1')
 
1293
 
 
1294
    def test_search(self):
 
1295
        """The search argument may be a 'search' of some explicit keys."""
 
1296
        backing = self.get_transport()
 
1297
        request = smart.repository.SmartServerRepositoryGetStream(backing)
 
1298
        repo, r1, r2 = self.make_two_commit_repo()
 
1299
        fetch_spec = ['search', '%s %s' % (r1, r2), 'null:', '2']
 
1300
        lines = '\n'.join(fetch_spec)
 
1301
        request.execute('', repo._format.network_name())
 
1302
        response = request.do_body(lines)
 
1303
        self.assertEqual(('ok',), response.args)
 
1304
        stream_bytes = ''.join(response.body_stream)
 
1305
        self.assertStartsWith(stream_bytes, 'Bazaar pack format 1')
 
1306
 
 
1307
 
 
1308
class TestSmartServerRequestHasRevision(tests.TestCaseWithMemoryTransport):
 
1309
 
 
1310
    def test_missing_revision(self):
 
1311
        """For a missing revision, ('no', ) is returned."""
 
1312
        backing = self.get_transport()
 
1313
        request = smart.repository.SmartServerRequestHasRevision(backing)
 
1314
        self.make_repository('.')
 
1315
        self.assertEqual(SmartServerResponse(('no', )),
 
1316
            request.execute('', 'revid'))
 
1317
 
 
1318
    def test_present_revision(self):
 
1319
        """For a present revision, ('yes', ) is returned."""
 
1320
        backing = self.get_transport()
 
1321
        request = smart.repository.SmartServerRequestHasRevision(backing)
 
1322
        tree = self.make_branch_and_memory_tree('.')
 
1323
        tree.lock_write()
 
1324
        tree.add('')
 
1325
        rev_id_utf8 = u'\xc8abc'.encode('utf-8')
 
1326
        r1 = tree.commit('a commit', rev_id=rev_id_utf8)
 
1327
        tree.unlock()
 
1328
        self.assertTrue(tree.branch.repository.has_revision(rev_id_utf8))
 
1329
        self.assertEqual(SmartServerResponse(('yes', )),
 
1330
            request.execute('', rev_id_utf8))
 
1331
 
 
1332
 
 
1333
class TestSmartServerRepositoryGatherStats(tests.TestCaseWithMemoryTransport):
 
1334
 
 
1335
    def test_empty_revid(self):
 
1336
        """With an empty revid, we get only size an number and revisions"""
 
1337
        backing = self.get_transport()
 
1338
        request = smart.repository.SmartServerRepositoryGatherStats(backing)
 
1339
        repository = self.make_repository('.')
 
1340
        stats = repository.gather_stats()
 
1341
        expected_body = 'revisions: 0\n'
 
1342
        self.assertEqual(SmartServerResponse(('ok', ), expected_body),
 
1343
                         request.execute('', '', 'no'))
 
1344
 
 
1345
    def test_revid_with_committers(self):
 
1346
        """For a revid we get more infos."""
 
1347
        backing = self.get_transport()
 
1348
        rev_id_utf8 = u'\xc8abc'.encode('utf-8')
 
1349
        request = smart.repository.SmartServerRepositoryGatherStats(backing)
 
1350
        tree = self.make_branch_and_memory_tree('.')
 
1351
        tree.lock_write()
 
1352
        tree.add('')
 
1353
        # Let's build a predictable result
 
1354
        tree.commit('a commit', timestamp=123456.2, timezone=3600)
 
1355
        tree.commit('a commit', timestamp=654321.4, timezone=0,
 
1356
                    rev_id=rev_id_utf8)
 
1357
        tree.unlock()
 
1358
 
 
1359
        stats = tree.branch.repository.gather_stats()
 
1360
        expected_body = ('firstrev: 123456.200 3600\n'
 
1361
                         'latestrev: 654321.400 0\n'
 
1362
                         'revisions: 2\n')
 
1363
        self.assertEqual(SmartServerResponse(('ok', ), expected_body),
 
1364
                         request.execute('',
 
1365
                                         rev_id_utf8, 'no'))
 
1366
 
 
1367
    def test_not_empty_repository_with_committers(self):
 
1368
        """For a revid and requesting committers we get the whole thing."""
 
1369
        backing = self.get_transport()
 
1370
        rev_id_utf8 = u'\xc8abc'.encode('utf-8')
 
1371
        request = smart.repository.SmartServerRepositoryGatherStats(backing)
 
1372
        tree = self.make_branch_and_memory_tree('.')
 
1373
        tree.lock_write()
 
1374
        tree.add('')
 
1375
        # Let's build a predictable result
 
1376
        tree.commit('a commit', timestamp=123456.2, timezone=3600,
 
1377
                    committer='foo')
 
1378
        tree.commit('a commit', timestamp=654321.4, timezone=0,
 
1379
                    committer='bar', rev_id=rev_id_utf8)
 
1380
        tree.unlock()
 
1381
        stats = tree.branch.repository.gather_stats()
 
1382
 
 
1383
        expected_body = ('committers: 2\n'
 
1384
                         'firstrev: 123456.200 3600\n'
 
1385
                         'latestrev: 654321.400 0\n'
 
1386
                         'revisions: 2\n')
 
1387
        self.assertEqual(SmartServerResponse(('ok', ), expected_body),
 
1388
                         request.execute('',
 
1389
                                         rev_id_utf8, 'yes'))
 
1390
 
 
1391
 
 
1392
class TestSmartServerRepositoryIsShared(tests.TestCaseWithMemoryTransport):
 
1393
 
 
1394
    def test_is_shared(self):
 
1395
        """For a shared repository, ('yes', ) is returned."""
 
1396
        backing = self.get_transport()
 
1397
        request = smart.repository.SmartServerRepositoryIsShared(backing)
 
1398
        self.make_repository('.', shared=True)
 
1399
        self.assertEqual(SmartServerResponse(('yes', )),
 
1400
            request.execute('', ))
 
1401
 
 
1402
    def test_is_not_shared(self):
 
1403
        """For a shared repository, ('no', ) is returned."""
 
1404
        backing = self.get_transport()
 
1405
        request = smart.repository.SmartServerRepositoryIsShared(backing)
 
1406
        self.make_repository('.', shared=False)
 
1407
        self.assertEqual(SmartServerResponse(('no', )),
 
1408
            request.execute('', ))
 
1409
 
 
1410
 
 
1411
class TestSmartServerRepositoryLockWrite(tests.TestCaseWithMemoryTransport):
 
1412
 
 
1413
    def test_lock_write_on_unlocked_repo(self):
 
1414
        backing = self.get_transport()
 
1415
        request = smart.repository.SmartServerRepositoryLockWrite(backing)
 
1416
        repository = self.make_repository('.', format='knit')
 
1417
        response = request.execute('')
 
1418
        nonce = repository.control_files._lock.peek().get('nonce')
 
1419
        self.assertEqual(SmartServerResponse(('ok', nonce)), response)
 
1420
        # The repository is now locked.  Verify that with a new repository
 
1421
        # object.
 
1422
        new_repo = repository.bzrdir.open_repository()
 
1423
        self.assertRaises(errors.LockContention, new_repo.lock_write)
 
1424
        # Cleanup
 
1425
        request = smart.repository.SmartServerRepositoryUnlock(backing)
 
1426
        response = request.execute('', nonce)
 
1427
 
 
1428
    def test_lock_write_on_locked_repo(self):
 
1429
        backing = self.get_transport()
 
1430
        request = smart.repository.SmartServerRepositoryLockWrite(backing)
 
1431
        repository = self.make_repository('.', format='knit')
 
1432
        repo_token = repository.lock_write()
 
1433
        repository.leave_lock_in_place()
 
1434
        repository.unlock()
 
1435
        response = request.execute('')
 
1436
        self.assertEqual(
 
1437
            SmartServerResponse(('LockContention',)), response)
 
1438
        # Cleanup
 
1439
        repository.lock_write(repo_token)
 
1440
        repository.dont_leave_lock_in_place()
 
1441
        repository.unlock()
 
1442
 
 
1443
    def test_lock_write_on_readonly_transport(self):
 
1444
        backing = self.get_readonly_transport()
 
1445
        request = smart.repository.SmartServerRepositoryLockWrite(backing)
 
1446
        repository = self.make_repository('.', format='knit')
 
1447
        response = request.execute('')
 
1448
        self.assertFalse(response.is_successful())
 
1449
        self.assertEqual('LockFailed', response.args[0])
 
1450
 
 
1451
 
 
1452
class TestInsertStreamBase(tests.TestCaseWithMemoryTransport):
 
1453
 
 
1454
    def make_empty_byte_stream(self, repo):
 
1455
        byte_stream = smart.repository._stream_to_byte_stream([], repo._format)
 
1456
        return ''.join(byte_stream)
 
1457
 
 
1458
 
 
1459
class TestSmartServerRepositoryInsertStream(TestInsertStreamBase):
 
1460
 
 
1461
    def test_insert_stream_empty(self):
 
1462
        backing = self.get_transport()
 
1463
        request = smart.repository.SmartServerRepositoryInsertStream(backing)
 
1464
        repository = self.make_repository('.')
 
1465
        response = request.execute('', '')
 
1466
        self.assertEqual(None, response)
 
1467
        response = request.do_chunk(self.make_empty_byte_stream(repository))
 
1468
        self.assertEqual(None, response)
 
1469
        response = request.do_end()
 
1470
        self.assertEqual(SmartServerResponse(('ok', )), response)
 
1471
        
 
1472
 
 
1473
class TestSmartServerRepositoryInsertStreamLocked(TestInsertStreamBase):
 
1474
 
 
1475
    def test_insert_stream_empty(self):
 
1476
        backing = self.get_transport()
 
1477
        request = smart.repository.SmartServerRepositoryInsertStreamLocked(
 
1478
            backing)
 
1479
        repository = self.make_repository('.', format='knit')
 
1480
        lock_token = repository.lock_write()
 
1481
        response = request.execute('', '', lock_token)
 
1482
        self.assertEqual(None, response)
 
1483
        response = request.do_chunk(self.make_empty_byte_stream(repository))
 
1484
        self.assertEqual(None, response)
 
1485
        response = request.do_end()
 
1486
        self.assertEqual(SmartServerResponse(('ok', )), response)
 
1487
        repository.unlock()
 
1488
 
 
1489
    def test_insert_stream_with_wrong_lock_token(self):
 
1490
        backing = self.get_transport()
 
1491
        request = smart.repository.SmartServerRepositoryInsertStreamLocked(
 
1492
            backing)
 
1493
        repository = self.make_repository('.', format='knit')
 
1494
        lock_token = repository.lock_write()
 
1495
        self.assertRaises(
 
1496
            errors.TokenMismatch, request.execute, '', '', 'wrong-token')
 
1497
        repository.unlock()
 
1498
 
 
1499
 
 
1500
class TestSmartServerRepositoryUnlock(tests.TestCaseWithMemoryTransport):
 
1501
 
 
1502
    def setUp(self):
 
1503
        tests.TestCaseWithMemoryTransport.setUp(self)
 
1504
 
 
1505
    def test_unlock_on_locked_repo(self):
 
1506
        backing = self.get_transport()
 
1507
        request = smart.repository.SmartServerRepositoryUnlock(backing)
 
1508
        repository = self.make_repository('.', format='knit')
 
1509
        token = repository.lock_write()
 
1510
        repository.leave_lock_in_place()
 
1511
        repository.unlock()
 
1512
        response = request.execute('', token)
 
1513
        self.assertEqual(
 
1514
            SmartServerResponse(('ok',)), response)
 
1515
        # The repository is now unlocked.  Verify that with a new repository
 
1516
        # object.
 
1517
        new_repo = repository.bzrdir.open_repository()
 
1518
        new_repo.lock_write()
 
1519
        new_repo.unlock()
 
1520
 
 
1521
    def test_unlock_on_unlocked_repo(self):
 
1522
        backing = self.get_transport()
 
1523
        request = smart.repository.SmartServerRepositoryUnlock(backing)
 
1524
        repository = self.make_repository('.', format='knit')
 
1525
        response = request.execute('', 'some token')
 
1526
        self.assertEqual(
 
1527
            SmartServerResponse(('TokenMismatch',)), response)
 
1528
 
 
1529
 
 
1530
class TestSmartServerIsReadonly(tests.TestCaseWithMemoryTransport):
 
1531
 
 
1532
    def test_is_readonly_no(self):
 
1533
        backing = self.get_transport()
 
1534
        request = smart.request.SmartServerIsReadonly(backing)
 
1535
        response = request.execute()
 
1536
        self.assertEqual(
 
1537
            SmartServerResponse(('no',)), response)
 
1538
 
 
1539
    def test_is_readonly_yes(self):
 
1540
        backing = self.get_readonly_transport()
 
1541
        request = smart.request.SmartServerIsReadonly(backing)
 
1542
        response = request.execute()
 
1543
        self.assertEqual(
 
1544
            SmartServerResponse(('yes',)), response)
 
1545
 
 
1546
 
 
1547
class TestSmartServerRepositorySetMakeWorkingTrees(tests.TestCaseWithMemoryTransport):
 
1548
 
 
1549
    def test_set_false(self):
 
1550
        backing = self.get_transport()
 
1551
        repo = self.make_repository('.', shared=True)
 
1552
        repo.set_make_working_trees(True)
 
1553
        request_class = smart.repository.SmartServerRepositorySetMakeWorkingTrees
 
1554
        request = request_class(backing)
 
1555
        self.assertEqual(SuccessfulSmartServerResponse(('ok',)),
 
1556
            request.execute('', 'False'))
 
1557
        repo = repo.bzrdir.open_repository()
 
1558
        self.assertFalse(repo.make_working_trees())
 
1559
 
 
1560
    def test_set_true(self):
 
1561
        backing = self.get_transport()
 
1562
        repo = self.make_repository('.', shared=True)
 
1563
        repo.set_make_working_trees(False)
 
1564
        request_class = smart.repository.SmartServerRepositorySetMakeWorkingTrees
 
1565
        request = request_class(backing)
 
1566
        self.assertEqual(SuccessfulSmartServerResponse(('ok',)),
 
1567
            request.execute('', 'True'))
 
1568
        repo = repo.bzrdir.open_repository()
 
1569
        self.assertTrue(repo.make_working_trees())
 
1570
 
 
1571
 
 
1572
class TestSmartServerPackRepositoryAutopack(tests.TestCaseWithTransport):
 
1573
 
 
1574
    def make_repo_needing_autopacking(self, path='.'):
 
1575
        # Make a repo in need of autopacking.
 
1576
        tree = self.make_branch_and_tree('.', format='pack-0.92')
 
1577
        repo = tree.branch.repository
 
1578
        # monkey-patch the pack collection to disable autopacking
 
1579
        repo._pack_collection._max_pack_count = lambda count: count
 
1580
        for x in range(10):
 
1581
            tree.commit('commit %s' % x)
 
1582
        self.assertEqual(10, len(repo._pack_collection.names()))
 
1583
        del repo._pack_collection._max_pack_count
 
1584
        return repo
 
1585
 
 
1586
    def test_autopack_needed(self):
 
1587
        repo = self.make_repo_needing_autopacking()
 
1588
        repo.lock_write()
 
1589
        self.addCleanup(repo.unlock)
 
1590
        backing = self.get_transport()
 
1591
        request = smart.packrepository.SmartServerPackRepositoryAutopack(
 
1592
            backing)
 
1593
        response = request.execute('')
 
1594
        self.assertEqual(SmartServerResponse(('ok',)), response)
 
1595
        repo._pack_collection.reload_pack_names()
 
1596
        self.assertEqual(1, len(repo._pack_collection.names()))
 
1597
 
 
1598
    def test_autopack_not_needed(self):
 
1599
        tree = self.make_branch_and_tree('.', format='pack-0.92')
 
1600
        repo = tree.branch.repository
 
1601
        repo.lock_write()
 
1602
        self.addCleanup(repo.unlock)
 
1603
        for x in range(9):
 
1604
            tree.commit('commit %s' % x)
 
1605
        backing = self.get_transport()
 
1606
        request = smart.packrepository.SmartServerPackRepositoryAutopack(
 
1607
            backing)
 
1608
        response = request.execute('')
 
1609
        self.assertEqual(SmartServerResponse(('ok',)), response)
 
1610
        repo._pack_collection.reload_pack_names()
 
1611
        self.assertEqual(9, len(repo._pack_collection.names()))
 
1612
 
 
1613
    def test_autopack_on_nonpack_format(self):
 
1614
        """A request to autopack a non-pack repo is a no-op."""
 
1615
        repo = self.make_repository('.', format='knit')
 
1616
        backing = self.get_transport()
 
1617
        request = smart.packrepository.SmartServerPackRepositoryAutopack(
 
1618
            backing)
 
1619
        response = request.execute('')
 
1620
        self.assertEqual(SmartServerResponse(('ok',)), response)
 
1621
 
 
1622
 
 
1623
class TestHandlers(tests.TestCase):
 
1624
    """Tests for the request.request_handlers object."""
 
1625
 
 
1626
    def test_all_registrations_exist(self):
 
1627
        """All registered request_handlers can be found."""
 
1628
        # If there's a typo in a register_lazy call, this loop will fail with
 
1629
        # an AttributeError.
 
1630
        for key, item in smart.request.request_handlers.iteritems():
 
1631
            pass
 
1632
 
 
1633
    def assertHandlerEqual(self, verb, handler):
 
1634
        self.assertEqual(smart.request.request_handlers.get(verb), handler)
 
1635
 
 
1636
    def test_registered_methods(self):
 
1637
        """Test that known methods are registered to the correct object."""
 
1638
        self.assertHandlerEqual('Branch.get_config_file',
 
1639
            smart.branch.SmartServerBranchGetConfigFile)
 
1640
        self.assertHandlerEqual('Branch.get_parent',
 
1641
            smart.branch.SmartServerBranchGetParent)
 
1642
        self.assertHandlerEqual('Branch.get_tags_bytes',
 
1643
            smart.branch.SmartServerBranchGetTagsBytes)
 
1644
        self.assertHandlerEqual('Branch.lock_write',
 
1645
            smart.branch.SmartServerBranchRequestLockWrite)
 
1646
        self.assertHandlerEqual('Branch.last_revision_info',
 
1647
            smart.branch.SmartServerBranchRequestLastRevisionInfo)
 
1648
        self.assertHandlerEqual('Branch.revision_history',
 
1649
            smart.branch.SmartServerRequestRevisionHistory)
 
1650
        self.assertHandlerEqual('Branch.set_config_option',
 
1651
            smart.branch.SmartServerBranchRequestSetConfigOption)
 
1652
        self.assertHandlerEqual('Branch.set_last_revision',
 
1653
            smart.branch.SmartServerBranchRequestSetLastRevision)
 
1654
        self.assertHandlerEqual('Branch.set_last_revision_info',
 
1655
            smart.branch.SmartServerBranchRequestSetLastRevisionInfo)
 
1656
        self.assertHandlerEqual('Branch.set_last_revision_ex',
 
1657
            smart.branch.SmartServerBranchRequestSetLastRevisionEx)
 
1658
        self.assertHandlerEqual('Branch.set_parent_location',
 
1659
            smart.branch.SmartServerBranchRequestSetParentLocation)
 
1660
        self.assertHandlerEqual('Branch.unlock',
 
1661
            smart.branch.SmartServerBranchRequestUnlock)
 
1662
        self.assertHandlerEqual('BzrDir.find_repository',
 
1663
            smart.bzrdir.SmartServerRequestFindRepositoryV1)
 
1664
        self.assertHandlerEqual('BzrDir.find_repositoryV2',
 
1665
            smart.bzrdir.SmartServerRequestFindRepositoryV2)
 
1666
        self.assertHandlerEqual('BzrDirFormat.initialize',
 
1667
            smart.bzrdir.SmartServerRequestInitializeBzrDir)
 
1668
        self.assertHandlerEqual('BzrDirFormat.initialize_ex_1.16',
 
1669
            smart.bzrdir.SmartServerRequestBzrDirInitializeEx)
 
1670
        self.assertHandlerEqual('BzrDir.cloning_metadir',
 
1671
            smart.bzrdir.SmartServerBzrDirRequestCloningMetaDir)
 
1672
        self.assertHandlerEqual('BzrDir.get_config_file',
 
1673
            smart.bzrdir.SmartServerBzrDirRequestConfigFile)
 
1674
        self.assertHandlerEqual('BzrDir.open_branch',
 
1675
            smart.bzrdir.SmartServerRequestOpenBranch)
 
1676
        self.assertHandlerEqual('BzrDir.open_branchV2',
 
1677
            smart.bzrdir.SmartServerRequestOpenBranchV2)
 
1678
        self.assertHandlerEqual('PackRepository.autopack',
 
1679
            smart.packrepository.SmartServerPackRepositoryAutopack)
 
1680
        self.assertHandlerEqual('Repository.gather_stats',
 
1681
            smart.repository.SmartServerRepositoryGatherStats)
 
1682
        self.assertHandlerEqual('Repository.get_parent_map',
 
1683
            smart.repository.SmartServerRepositoryGetParentMap)
 
1684
        self.assertHandlerEqual('Repository.get_rev_id_for_revno',
 
1685
            smart.repository.SmartServerRepositoryGetRevIdForRevno)
 
1686
        self.assertHandlerEqual('Repository.get_revision_graph',
 
1687
            smart.repository.SmartServerRepositoryGetRevisionGraph)
 
1688
        self.assertHandlerEqual('Repository.get_stream',
 
1689
            smart.repository.SmartServerRepositoryGetStream)
 
1690
        self.assertHandlerEqual('Repository.has_revision',
 
1691
            smart.repository.SmartServerRequestHasRevision)
 
1692
        self.assertHandlerEqual('Repository.insert_stream',
 
1693
            smart.repository.SmartServerRepositoryInsertStream)
 
1694
        self.assertHandlerEqual('Repository.insert_stream_locked',
 
1695
            smart.repository.SmartServerRepositoryInsertStreamLocked)
 
1696
        self.assertHandlerEqual('Repository.is_shared',
 
1697
            smart.repository.SmartServerRepositoryIsShared)
 
1698
        self.assertHandlerEqual('Repository.lock_write',
 
1699
            smart.repository.SmartServerRepositoryLockWrite)
 
1700
        self.assertHandlerEqual('Repository.tarball',
 
1701
            smart.repository.SmartServerRepositoryTarball)
 
1702
        self.assertHandlerEqual('Repository.unlock',
 
1703
            smart.repository.SmartServerRepositoryUnlock)
 
1704
        self.assertHandlerEqual('Transport.is_readonly',
 
1705
            smart.request.SmartServerIsReadonly)