~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

(jameinel) Allow 'bzr serve' to interpret SIGHUP as a graceful shutdown.
 (bug #795025) (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
from cStringIO import StringIO
18
18
import os
22
22
 
23
23
from bzrlib import (
24
24
    bzrdir,
 
25
    diff,
25
26
    errors,
26
27
    inventory,
 
28
    merge,
27
29
    osutils,
28
 
    repository,
29
30
    revision as _mod_revision,
 
31
    symbol_versioning,
 
32
    tests,
30
33
    treebuilder,
31
34
    )
32
35
from bzrlib.bundle import read_mergeable_from_url
33
36
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
34
37
from bzrlib.bundle.bundle_data import BundleTree
35
 
from bzrlib.bzrdir import BzrDir
36
38
from bzrlib.directory_service import directories
37
39
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
38
40
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
39
41
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
40
42
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
41
 
from bzrlib.branch import Branch
42
 
from bzrlib.diff import internal_diff
43
 
from bzrlib.merge import Merge3Merger
44
43
from bzrlib.repofmt import knitrepo
45
 
from bzrlib.osutils import sha_file, sha_string
46
44
from bzrlib.tests import (
47
 
    SymlinkFeature,
48
 
    TestCase,
49
 
    TestCaseInTempDir,
50
 
    TestCaseWithTransport,
51
 
    TestSkipped,
52
45
    test_read_bundle,
53
46
    test_commit,
54
47
    )
55
48
from bzrlib.transform import TreeTransform
 
49
from bzrlib.tests import (
 
50
    features,
 
51
    )
 
52
 
 
53
 
 
54
def get_text(vf, key):
 
55
    """Get the fulltext for a given revision id that is present in the vf"""
 
56
    stream = vf.get_record_stream([key], 'unordered', True)
 
57
    record = stream.next()
 
58
    return record.get_bytes_as('fulltext')
 
59
 
 
60
 
 
61
def get_inventory_text(repo, revision_id):
 
62
    """Get the fulltext for the inventory at revision id"""
 
63
    repo.lock_read()
 
64
    try:
 
65
        return get_text(repo.inventories, (revision_id,))
 
66
    finally:
 
67
        repo.unlock()
56
68
 
57
69
 
58
70
class MockTree(object):
 
71
 
59
72
    def __init__(self):
60
73
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
61
74
        object.__init__(self)
66
79
 
67
80
    inventory = property(lambda x:x)
68
81
 
69
 
    def __iter__(self):
70
 
        return self.paths.iterkeys()
 
82
    def all_file_ids(self):
 
83
        return set(self.paths.keys())
71
84
 
72
85
    def __getitem__(self, file_id):
73
86
        if file_id == self.root.file_id:
103
116
            ie = InventoryDirectory(file_id, name, parent_id)
104
117
        elif kind == 'file':
105
118
            ie = InventoryFile(file_id, name, parent_id)
 
119
            ie.text_sha1 = text_sha_1
 
120
            ie.text_size = text_size
106
121
        elif kind == 'symlink':
107
122
            ie = InventoryLink(file_id, name, parent_id)
108
123
        else:
109
124
            raise errors.BzrError('unknown kind %r' % kind)
110
 
        ie.text_sha1 = text_sha_1
111
 
        ie.text_size = text_size
112
125
        return ie
113
126
 
114
127
    def add_dir(self, file_id, path):
115
128
        self.paths[file_id] = path
116
129
        self.ids[path] = file_id
117
 
    
 
130
 
118
131
    def add_file(self, file_id, path, contents):
119
132
        self.add_dir(file_id, path)
120
133
        self.contents[file_id] = contents
134
147
        result.seek(0,0)
135
148
        return result
136
149
 
 
150
    def get_file_revision(self, file_id):
 
151
        return self.inventory[file_id].revision
 
152
 
137
153
    def contents_stats(self, file_id):
138
154
        if file_id not in self.contents:
139
155
            return None, None
140
 
        text_sha1 = sha_file(self.get_file(file_id))
 
156
        text_sha1 = osutils.sha_file(self.get_file(file_id))
141
157
        return text_sha1, len(self.contents[file_id])
142
158
 
143
159
 
144
 
class BTreeTester(TestCase):
 
160
class BTreeTester(tests.TestCase):
145
161
    """A simple unittest tester for the BundleTree class."""
146
162
 
147
163
    def make_tree_1(self):
151
167
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
152
168
        mtree.add_dir("d", "grandparent/alt_parent")
153
169
        return BundleTree(mtree, ''), mtree
154
 
        
 
170
 
155
171
    def test_renames(self):
156
172
        """Ensure that file renames have the proper effect on children"""
157
173
        btree = self.make_tree_1()[0]
158
174
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
159
 
        self.assertEqual(btree.old_path("grandparent/parent"), 
 
175
        self.assertEqual(btree.old_path("grandparent/parent"),
160
176
                         "grandparent/parent")
161
177
        self.assertEqual(btree.old_path("grandparent/parent/file"),
162
178
                         "grandparent/parent/file")
202
218
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
203
219
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
204
220
 
205
 
        btree.note_rename("grandparent/parent/file", 
 
221
        btree.note_rename("grandparent/parent/file",
206
222
                          "grandparent2/parent2/file2")
207
223
        self.assertEqual(btree.id2path("a"), "grandparent2")
208
224
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
217
233
    def test_moves(self):
218
234
        """Ensure that file moves have the proper effect on children"""
219
235
        btree = self.make_tree_1()[0]
220
 
        btree.note_rename("grandparent/parent/file", 
 
236
        btree.note_rename("grandparent/parent/file",
221
237
                          "grandparent/alt_parent/file")
222
238
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
223
239
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
225
241
 
226
242
    def unified_diff(self, old, new):
227
243
        out = StringIO()
228
 
        internal_diff("old", old, "new", new, out)
 
244
        diff.internal_diff("old", old, "new", new, out)
229
245
        out.seek(0,0)
230
246
        return out.read()
231
247
 
232
248
    def make_tree_2(self):
233
249
        btree = self.make_tree_1()[0]
234
 
        btree.note_rename("grandparent/parent/file", 
 
250
        btree.note_rename("grandparent/parent/file",
235
251
                          "grandparent/alt_parent/file")
236
252
        self.assertTrue(btree.id2path("e") is None)
237
253
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
266
282
    def make_tree_3(self):
267
283
        btree, mtree = self.make_tree_1()
268
284
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
269
 
        btree.note_rename("grandparent/parent/file", 
 
285
        btree.note_rename("grandparent/parent/file",
270
286
                          "grandparent/alt_parent/file")
271
 
        btree.note_rename("grandparent/parent/topping", 
 
287
        btree.note_rename("grandparent/parent/topping",
272
288
                          "grandparent/alt_parent/stopping")
273
289
        return btree
274
290
 
313
329
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
314
330
        btree.note_deletion("grandparent/parent/file")
315
331
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
316
 
        btree.note_last_changed("grandparent/alt_parent/fool", 
 
332
        btree.note_last_changed("grandparent/alt_parent/fool",
317
333
                                "revisionidiguess")
318
334
        self.assertEqual(self.sorted_ids(btree),
319
335
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
320
336
 
321
337
 
322
 
class BundleTester1(TestCaseWithTransport):
 
338
class BundleTester1(tests.TestCaseWithTransport):
323
339
 
324
340
    def test_mismatched_bundle(self):
325
341
        format = bzrdir.BzrDirMetaFormat1()
326
342
        format.repository_format = knitrepo.RepositoryFormatKnit3()
327
343
        serializer = BundleSerializerV08('0.8')
328
344
        b = self.make_branch('.', format=format)
329
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
 
345
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
330
346
                          b.repository, [], {}, StringIO())
331
347
 
332
348
    def test_matched_bundle(self):
352
368
        format = bzrdir.BzrDirMetaFormat1()
353
369
        format.repository_format = knitrepo.RepositoryFormatKnit1()
354
370
        target = self.make_branch('target', format=format)
355
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
 
371
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
356
372
                          target.repository, read_bundle(text))
357
373
 
358
374
 
366
382
    def make_branch_and_tree(self, path, format=None):
367
383
        if format is None:
368
384
            format = self.bzrdir_format()
369
 
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
 
385
        return tests.TestCaseWithTransport.make_branch_and_tree(
 
386
            self, path, format)
370
387
 
371
388
    def make_branch(self, path, format=None):
372
389
        if format is None:
373
390
            format = self.bzrdir_format()
374
 
        return TestCaseWithTransport.make_branch(self, path, format)
 
391
        return tests.TestCaseWithTransport.make_branch(self, path, format)
375
392
 
376
393
    def create_bundle_text(self, base_rev_id, rev_id):
377
394
        bundle_txt = StringIO()
378
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
395
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
379
396
                               bundle_txt, format=self.format)
380
397
        bundle_txt.seek(0)
381
 
        self.assertEqual(bundle_txt.readline(), 
 
398
        self.assertEqual(bundle_txt.readline(),
382
399
                         '# Bazaar revision bundle v%s\n' % self.format)
383
400
        self.assertEqual(bundle_txt.readline(), '#\n')
384
401
 
392
409
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
393
410
        Make sure that the text generated is valid, and that it
394
411
        can be applied against the base, and generate the same information.
395
 
        
396
 
        :return: The in-memory bundle 
 
412
 
 
413
        :return: The in-memory bundle
397
414
        """
398
415
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
399
416
 
400
 
        # This should also validate the generated bundle 
 
417
        # This should also validate the generated bundle
401
418
        bundle = read_bundle(bundle_txt)
402
419
        repository = self.b1.repository
403
420
        for bundle_rev in bundle.real_revisions:
407
424
            # it
408
425
            branch_rev = repository.get_revision(bundle_rev.revision_id)
409
426
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
410
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
427
                      'timestamp', 'timezone', 'message', 'committer',
411
428
                      'parent_ids', 'properties'):
412
 
                self.assertEqual(getattr(branch_rev, a), 
 
429
                self.assertEqual(getattr(branch_rev, a),
413
430
                                 getattr(bundle_rev, a))
414
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
431
            self.assertEqual(len(branch_rev.parent_ids),
415
432
                             len(bundle_rev.parent_ids))
416
 
        self.assertEqual(rev_ids, 
 
433
        self.assertEqual(rev_ids,
417
434
                         [r.revision_id for r in bundle.real_revisions])
418
435
        self.valid_apply_bundle(base_rev_id, bundle,
419
436
                                   checkout_dir=checkout_dir)
423
440
    def get_invalid_bundle(self, base_rev_id, rev_id):
424
441
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
425
442
        Munge the text so that it's invalid.
426
 
        
 
443
 
427
444
        :return: The in-memory bundle
428
445
        """
429
446
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
430
 
        new_text = bundle_txt.getvalue().replace('executable:no', 
 
447
        new_text = bundle_txt.getvalue().replace('executable:no',
431
448
                                               'executable:yes')
432
449
        bundle_txt = StringIO(new_text)
433
450
        bundle = read_bundle(bundle_txt)
434
451
        self.valid_apply_bundle(base_rev_id, bundle)
435
 
        return bundle 
 
452
        return bundle
436
453
 
437
454
    def test_non_bundle(self):
438
455
        self.assertRaises(errors.NotABundle,
480
497
                                 % (ancestor,))
481
498
 
482
499
                # Now check that the file contents are all correct
483
 
                for inventory_id in old:
 
500
                for inventory_id in old.all_file_ids():
484
501
                    try:
485
502
                        old_file = old.get_file(inventory_id)
486
503
                    except errors.NoSuchFile:
494
511
                old.unlock()
495
512
        if not _mod_revision.is_null(rev_id):
496
513
            rh = self.b1.revision_history()
497
 
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
 
514
            self.applyDeprecated(symbol_versioning.deprecated_in((2, 4, 0)),
 
515
                tree.branch.set_revision_history, rh[:rh.index(rev_id)+1])
498
516
            tree.update()
499
517
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
500
518
            self.assertFalse(delta.has_changed(),
519
537
        self.assertIs(repository.has_revision(base_rev_id), True)
520
538
        for rev in info.real_revisions:
521
539
            self.assert_(not repository.has_revision(rev.revision_id),
522
 
                'Revision {%s} present before applying bundle' 
 
540
                'Revision {%s} present before applying bundle'
523
541
                % rev.revision_id)
524
 
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
 
542
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
525
543
 
526
544
        for rev in info.real_revisions:
527
545
            self.assert_(repository.has_revision(rev.revision_id),
528
 
                'Missing revision {%s} after applying bundle' 
 
546
                'Missing revision {%s} after applying bundle'
529
547
                % rev.revision_id)
530
548
 
531
549
        self.assert_(to_tree.branch.repository.has_revision(info.target))
537
555
        rev = info.real_revisions[-1]
538
556
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
539
557
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
540
 
        
 
558
 
541
559
        # TODO: make sure the target tree is identical to base tree
542
560
        #       we might also check the working tree.
543
561
 
562
580
        self.tree1 = self.make_branch_and_tree('b1')
563
581
        self.b1 = self.tree1.branch
564
582
 
565
 
        open('b1/one', 'wb').write('one\n')
566
 
        self.tree1.add('one')
 
583
        self.build_tree_contents([('b1/one', 'one\n')])
 
584
        self.tree1.add('one', 'one-id')
 
585
        self.tree1.set_root_id('root-id')
567
586
        self.tree1.commit('add one', rev_id='a@cset-0-1')
568
587
 
569
588
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
580
599
                , 'b1/sub/sub/'
581
600
                , 'b1/sub/sub/nonempty.txt'
582
601
                ])
583
 
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
584
 
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
 
602
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
 
603
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
585
604
        tt = TreeTransform(self.tree1)
586
605
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
587
606
        tt.apply()
603
622
 
604
623
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
605
624
 
606
 
        # Check a rollup bundle 
 
625
        # Check a rollup bundle
607
626
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
608
627
 
609
628
        # Now delete entries
617
636
        tt.set_executability(False, trans_id)
618
637
        tt.apply()
619
638
        self.tree1.commit('removed', rev_id='a@cset-0-3')
620
 
        
 
639
 
621
640
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
622
641
        self.assertRaises((errors.TestamentMismatch,
623
 
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
 
642
            errors.VersionedFileInvalidChecksum,
 
643
            errors.BadBundle), self.get_invalid_bundle,
624
644
            'a@cset-0-2', 'a@cset-0-3')
625
 
        # Check a rollup bundle 
 
645
        # Check a rollup bundle
626
646
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
627
647
 
628
648
        # Now move the directory
630
650
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
631
651
 
632
652
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
633
 
        # Check a rollup bundle 
 
653
        # Check a rollup bundle
634
654
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
635
655
 
636
656
        # Modified files
638
658
        open('b1/sub/dir/ pre space', 'ab').write(
639
659
             '\r\nAdding some\r\nDOS format lines\r\n')
640
660
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
641
 
        self.tree1.rename_one('sub/dir/ pre space', 
 
661
        self.tree1.rename_one('sub/dir/ pre space',
642
662
                              'sub/ start space')
643
663
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
644
664
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
650
670
                          verbose=False)
651
671
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
652
672
        other = self.get_checkout('a@cset-0-5')
653
 
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
654
 
            'a@cset-0-5')
655
 
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
 
673
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
 
674
                                       'a@cset-0-5')
 
675
        tree2_inv = get_inventory_text(other.branch.repository,
 
676
                                       'a@cset-0-5')
656
677
        self.assertEqualDiff(tree1_inv, tree2_inv)
657
678
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
658
679
        other.commit('rename file', rev_id='a@cset-0-6b')
661
682
                          verbose=False)
662
683
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
663
684
 
664
 
    def test_symlink_bundle(self):
665
 
        self.requireFeature(SymlinkFeature)
 
685
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
 
686
        link_id = 'link-1'
 
687
 
 
688
        self.requireFeature(features.SymlinkFeature)
666
689
        self.tree1 = self.make_branch_and_tree('b1')
667
690
        self.b1 = self.tree1.branch
 
691
 
668
692
        tt = TreeTransform(self.tree1)
669
 
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
 
693
        tt.new_symlink(link_name, tt.root, link_target, link_id)
670
694
        tt.apply()
671
695
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
672
 
        self.get_valid_bundle('null:', 'l@cset-0-1')
 
696
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
 
697
        if getattr(bundle ,'revision_tree', None) is not None:
 
698
            # Not all bundle formats supports revision_tree
 
699
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
 
700
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
 
701
 
673
702
        tt = TreeTransform(self.tree1)
674
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
703
        trans_id = tt.trans_id_tree_file_id(link_id)
675
704
        tt.adjust_path('link2', tt.root, trans_id)
676
705
        tt.delete_contents(trans_id)
677
 
        tt.create_symlink('mars', trans_id)
 
706
        tt.create_symlink(new_link_target, trans_id)
678
707
        tt.apply()
679
708
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
680
 
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
709
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
710
        if getattr(bundle ,'revision_tree', None) is not None:
 
711
            # Not all bundle formats supports revision_tree
 
712
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
 
713
            self.assertEqual(new_link_target,
 
714
                             bund_tree.get_symlink_target(link_id))
 
715
 
681
716
        tt = TreeTransform(self.tree1)
682
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
717
        trans_id = tt.trans_id_tree_file_id(link_id)
683
718
        tt.delete_contents(trans_id)
684
719
        tt.create_symlink('jupiter', trans_id)
685
720
        tt.apply()
686
721
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
687
 
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
722
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
723
 
688
724
        tt = TreeTransform(self.tree1)
689
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
725
        trans_id = tt.trans_id_tree_file_id(link_id)
690
726
        tt.delete_contents(trans_id)
691
727
        tt.apply()
692
728
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
693
 
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
729
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
730
 
 
731
    def test_symlink_bundle(self):
 
732
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
 
733
 
 
734
    def test_unicode_symlink_bundle(self):
 
735
        self.requireFeature(features.UnicodeFilenameFeature)
 
736
        self._test_symlink_bundle(u'\N{Euro Sign}link',
 
737
                                  u'bar/\N{Euro Sign}foo',
 
738
                                  u'mars\N{Euro Sign}')
694
739
 
695
740
    def test_binary_bundle(self):
696
741
        self.tree1 = self.make_branch_and_tree('b1')
697
742
        self.b1 = self.tree1.branch
698
743
        tt = TreeTransform(self.tree1)
699
 
        
 
744
 
700
745
        # Add
701
746
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
702
747
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
794
839
        return bundle_file.getvalue()
795
840
 
796
841
    def test_unicode_bundle(self):
 
842
        self.requireFeature(features.UnicodeFilenameFeature)
797
843
        # Handle international characters
798
844
        os.mkdir('b1')
799
 
        try:
800
 
            f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
801
 
        except UnicodeEncodeError:
802
 
            raise TestSkipped("Filesystem doesn't support unicode")
 
845
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
803
846
 
804
847
        self.tree1 = self.make_branch_and_tree('b1')
805
848
        self.b1 = self.tree1.branch
843
886
 
844
887
    def test_whitespace_bundle(self):
845
888
        if sys.platform in ('win32', 'cygwin'):
846
 
            raise TestSkipped('Windows doesn\'t support filenames'
847
 
                              ' with tabs or trailing spaces')
 
889
            raise tests.TestSkipped('Windows doesn\'t support filenames'
 
890
                                    ' with tabs or trailing spaces')
848
891
        self.tree1 = self.make_branch_and_tree('b1')
849
892
        self.b1 = self.tree1.branch
850
893
 
875
918
        self.tree1.commit('removed', rev_id='white-4')
876
919
 
877
920
        bundle = self.get_valid_bundle('white-3', 'white-4')
878
 
        
 
921
 
879
922
        # Now test a complet roll-up
880
923
        bundle = self.get_valid_bundle('null:', 'white-4')
881
924
 
894
937
                          timezone=19800, timestamp=1152544886.0)
895
938
 
896
939
        bundle = self.get_valid_bundle('null:', 'tz-1')
897
 
        
 
940
 
898
941
        rev = bundle.revisions[0]
899
942
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
900
943
        self.assertEqual(19800, rev.timezone)
1002
1045
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1003
1046
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1004
1047
        bundle.install_revisions(repo)
1005
 
        inv_text = repo.get_inventory_xml('rev2')
 
1048
        inv_text = repo._get_inventory_xml('rev2')
1006
1049
        self.assertNotContainsRe(inv_text, 'format="5"')
1007
1050
        self.assertContainsRe(inv_text, 'format="7"')
1008
1051
 
1028
1071
 
1029
1072
    def test_inv_hash_across_serializers(self):
1030
1073
        repo = self.make_repo_with_installed_revisions()
1031
 
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
1032
 
        xml = repo.get_inventory_xml('rev2')
1033
 
        self.assertEqual(sha_string(xml), recorded_inv_sha1)
 
1074
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
 
1075
        xml = repo._get_inventory_xml('rev2')
 
1076
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
1034
1077
 
1035
1078
    def test_across_models_incompatible(self):
1036
1079
        tree = self.make_simple_tree('dirstate-with-subtree')
1039
1082
        try:
1040
1083
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1041
1084
        except errors.IncompatibleBundleFormat:
1042
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1085
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1043
1086
        repo = self.make_repository('repo', format='knit')
1044
1087
        bundle.install_revisions(repo)
1045
1088
 
1066
1109
        try:
1067
1110
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1068
1111
        except errors.IncompatibleBundleFormat:
1069
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1112
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1070
1113
        if isinstance(bundle, v09.BundleInfo09):
1071
 
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
 
1114
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
1072
1115
        repo = self.make_repository('repo', format='knit')
1073
1116
        self.assertRaises(errors.IncompatibleRevision,
1074
1117
                          bundle.install_revisions, repo)
1081
1124
        try:
1082
1125
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1083
1126
        except ValueError:
1084
 
            raise TestSkipped("Repository doesn't support revision ids with"
1085
 
                              " slashes")
 
1127
            raise tests.TestSkipped(
 
1128
                "Repository doesn't support revision ids with slashes")
1086
1129
        bundle = self.get_valid_bundle('null:', 'rev/id')
1087
1130
 
1088
1131
    def test_skip_file(self):
1104
1147
        self.tree1.commit('rev3', rev_id='rev3')
1105
1148
        bundle = self.get_valid_bundle('reva', 'rev3')
1106
1149
        if getattr(bundle, 'get_bundle_reader', None) is None:
1107
 
            raise TestSkipped('Bundle format cannot provide reader')
 
1150
            raise tests.TestSkipped('Bundle format cannot provide reader')
1108
1151
        # be sure that file1 comes before file2
1109
1152
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1110
1153
            if f == 'file3-id':
1113
1156
        bundle.install_revisions(target.branch.repository)
1114
1157
 
1115
1158
 
1116
 
class V08BundleTester(BundleTester, TestCaseWithTransport):
 
1159
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
1117
1160
 
1118
1161
    format = '0.8'
1119
1162
 
1252
1295
        return format
1253
1296
 
1254
1297
 
1255
 
class V4BundleTester(BundleTester, TestCaseWithTransport):
 
1298
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
1256
1299
 
1257
1300
    format = '4'
1258
1301
 
1260
1303
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1261
1304
        Make sure that the text generated is valid, and that it
1262
1305
        can be applied against the base, and generate the same information.
1263
 
        
1264
 
        :return: The in-memory bundle 
 
1306
 
 
1307
        :return: The in-memory bundle
1265
1308
        """
1266
1309
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1267
1310
 
1268
 
        # This should also validate the generated bundle 
 
1311
        # This should also validate the generated bundle
1269
1312
        bundle = read_bundle(bundle_txt)
1270
1313
        repository = self.b1.repository
1271
1314
        for bundle_rev in bundle.real_revisions:
1275
1318
            # it
1276
1319
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1277
1320
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1278
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
1321
                      'timestamp', 'timezone', 'message', 'committer',
1279
1322
                      'parent_ids', 'properties'):
1280
 
                self.assertEqual(getattr(branch_rev, a), 
 
1323
                self.assertEqual(getattr(branch_rev, a),
1281
1324
                                 getattr(bundle_rev, a))
1282
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
1325
            self.assertEqual(len(branch_rev.parent_ids),
1283
1326
                             len(bundle_rev.parent_ids))
1284
1327
        self.assertEqual(set(rev_ids),
1285
1328
                         set([r.revision_id for r in bundle.real_revisions]))
1299
1342
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1300
1343
        new_text = new_text.replace('<file file_id="exe-1"',
1301
1344
                                    '<file executable="y" file_id="exe-1"')
1302
 
        new_text = new_text.replace('B222', 'B237')
 
1345
        new_text = new_text.replace('B260', 'B275')
1303
1346
        bundle_txt = StringIO()
1304
1347
        bundle_txt.write(serializer._get_bundle_header('4'))
1305
1348
        bundle_txt.write('\n')
1311
1354
 
1312
1355
    def create_bundle_text(self, base_rev_id, rev_id):
1313
1356
        bundle_txt = StringIO()
1314
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
1357
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1315
1358
                               bundle_txt, format=self.format)
1316
1359
        bundle_txt.seek(0)
1317
 
        self.assertEqual(bundle_txt.readline(), 
 
1360
        self.assertEqual(bundle_txt.readline(),
1318
1361
                         '# Bazaar revision bundle v%s\n' % self.format)
1319
1362
        self.assertEqual(bundle_txt.readline(), '#\n')
1320
1363
        rev = self.b1.repository.get_revision(rev_id)
1342
1385
        install_bundle(target_repo, serializer.read(s))
1343
1386
        target_repo.lock_read()
1344
1387
        self.addCleanup(target_repo.unlock)
 
1388
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
 
1389
        repo_texts = dict((i, ''.join(content)) for i, content
 
1390
                          in target_repo.iter_files_bytes(
 
1391
                                [('fileid-2', 'rev1', '1'),
 
1392
                                 ('fileid-2', 'rev2', '2')]))
1345
1393
        self.assertEqual({'1':'contents1\nstatic\n',
1346
 
            '2':'contents2\nstatic\n'},
1347
 
            dict(target_repo.iter_files_bytes(
1348
 
                [('fileid-2', 'rev1', '1'), ('fileid-2', 'rev2', '2')])))
 
1394
                          '2':'contents2\nstatic\n'},
 
1395
                         repo_texts)
1349
1396
        rtree = target_repo.revision_tree('rev2')
1350
1397
        inventory_vf = target_repo.inventories
1351
1398
        # If the inventory store has a graph, it must match the revision graph.
1371
1418
        branch = tree_a.branch
1372
1419
        repo_a = branch.repository
1373
1420
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1374
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
1421
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
1375
1422
        try:
1376
1423
            from bzrlib.testament import Testament
1377
1424
            # monkey patch gpg signing mechanism
1401
1448
        install_bundle(repo_b, serializer.read(s))
1402
1449
 
1403
1450
 
1404
 
class V4WeaveBundleTester(V4BundleTester):
 
1451
class V4_2aBundleTester(V4BundleTester):
1405
1452
 
1406
1453
    def bzrdir_format(self):
1407
 
        return 'metaweave'
 
1454
        return '2a'
 
1455
 
 
1456
    def get_invalid_bundle(self, base_rev_id, rev_id):
 
1457
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1458
        Munge the text so that it's invalid.
 
1459
 
 
1460
        :return: The in-memory bundle
 
1461
        """
 
1462
        from bzrlib.bundle import serializer
 
1463
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1464
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
 
1465
        # We are going to be replacing some text to set the executable bit on a
 
1466
        # file. Make sure the text replacement actually works correctly.
 
1467
        self.assertContainsRe(new_text, '(?m)B244\n\ni 1\n<inventory')
 
1468
        new_text = new_text.replace('<file file_id="exe-1"',
 
1469
                                    '<file executable="y" file_id="exe-1"')
 
1470
        new_text = new_text.replace('B244', 'B259')
 
1471
        bundle_txt = StringIO()
 
1472
        bundle_txt.write(serializer._get_bundle_header('4'))
 
1473
        bundle_txt.write('\n')
 
1474
        bundle_txt.write(new_text.encode('bz2'))
 
1475
        bundle_txt.seek(0)
 
1476
        bundle = read_bundle(bundle_txt)
 
1477
        self.valid_apply_bundle(base_rev_id, bundle)
 
1478
        return bundle
 
1479
 
 
1480
    def make_merged_branch(self):
 
1481
        builder = self.make_branch_builder('source')
 
1482
        builder.start_series()
 
1483
        builder.build_snapshot('a@cset-0-1', None, [
 
1484
            ('add', ('', 'root-id', 'directory', None)),
 
1485
            ('add', ('file', 'file-id', 'file', 'original content\n')),
 
1486
            ])
 
1487
        builder.build_snapshot('a@cset-0-2a', ['a@cset-0-1'], [
 
1488
            ('modify', ('file-id', 'new-content\n')),
 
1489
            ])
 
1490
        builder.build_snapshot('a@cset-0-2b', ['a@cset-0-1'], [
 
1491
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
 
1492
            ])
 
1493
        builder.build_snapshot('a@cset-0-3', ['a@cset-0-2a', 'a@cset-0-2b'], [
 
1494
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
 
1495
            ])
 
1496
        builder.finish_series()
 
1497
        self.b1 = builder.get_branch()
 
1498
        self.b1.lock_read()
 
1499
        self.addCleanup(self.b1.unlock)
 
1500
 
 
1501
    def make_bundle_just_inventories(self, base_revision_id,
 
1502
                                     target_revision_id,
 
1503
                                     revision_ids):
 
1504
        sio = StringIO()
 
1505
        writer = v4.BundleWriteOperation(base_revision_id, target_revision_id,
 
1506
                                         self.b1.repository, sio)
 
1507
        writer.bundle.begin()
 
1508
        writer._add_inventory_mpdiffs_from_serializer(revision_ids)
 
1509
        writer.bundle.end()
 
1510
        sio.seek(0)
 
1511
        return sio
 
1512
 
 
1513
    def test_single_inventory_multiple_parents_as_xml(self):
 
1514
        self.make_merged_branch()
 
1515
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
 
1516
                                                ['a@cset-0-3'])
 
1517
        reader = v4.BundleReader(sio, stream_input=False)
 
1518
        records = list(reader.iter_records())
 
1519
        self.assertEqual(1, len(records))
 
1520
        (bytes, metadata, repo_kind, revision_id,
 
1521
         file_id) = records[0]
 
1522
        self.assertIs(None, file_id)
 
1523
        self.assertEqual('a@cset-0-3', revision_id)
 
1524
        self.assertEqual('inventory', repo_kind)
 
1525
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
 
1526
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1527
                          'storage_kind': 'mpdiff',
 
1528
                         }, metadata)
 
1529
        # We should have an mpdiff that takes some lines from both parents.
 
1530
        self.assertEqualDiff(
 
1531
            'i 1\n'
 
1532
            '<inventory format="10" revision_id="a@cset-0-3">\n'
 
1533
            '\n'
 
1534
            'c 0 1 1 2\n'
 
1535
            'c 1 3 3 2\n', bytes)
 
1536
 
 
1537
    def test_single_inv_no_parents_as_xml(self):
 
1538
        self.make_merged_branch()
 
1539
        sio = self.make_bundle_just_inventories('null:', 'a@cset-0-1',
 
1540
                                                ['a@cset-0-1'])
 
1541
        reader = v4.BundleReader(sio, stream_input=False)
 
1542
        records = list(reader.iter_records())
 
1543
        self.assertEqual(1, len(records))
 
1544
        (bytes, metadata, repo_kind, revision_id,
 
1545
         file_id) = records[0]
 
1546
        self.assertIs(None, file_id)
 
1547
        self.assertEqual('a@cset-0-1', revision_id)
 
1548
        self.assertEqual('inventory', repo_kind)
 
1549
        self.assertEqual({'parents': [],
 
1550
                          'sha1': 'a13f42b142d544aac9b085c42595d304150e31a2',
 
1551
                          'storage_kind': 'mpdiff',
 
1552
                         }, metadata)
 
1553
        # We should have an mpdiff that takes some lines from both parents.
 
1554
        self.assertEqualDiff(
 
1555
            'i 4\n'
 
1556
            '<inventory format="10" revision_id="a@cset-0-1">\n'
 
1557
            '<directory file_id="root-id" name=""'
 
1558
                ' revision="a@cset-0-1" />\n'
 
1559
            '<file file_id="file-id" name="file" parent_id="root-id"'
 
1560
                ' revision="a@cset-0-1"'
 
1561
                ' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
 
1562
                ' text_size="17" />\n'
 
1563
            '</inventory>\n'
 
1564
            '\n', bytes)
 
1565
 
 
1566
    def test_multiple_inventories_as_xml(self):
 
1567
        self.make_merged_branch()
 
1568
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
 
1569
            ['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'])
 
1570
        reader = v4.BundleReader(sio, stream_input=False)
 
1571
        records = list(reader.iter_records())
 
1572
        self.assertEqual(3, len(records))
 
1573
        revision_ids = [rev_id for b, m, k, rev_id, f in records]
 
1574
        self.assertEqual(['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'],
 
1575
                         revision_ids)
 
1576
        metadata_2a = records[0][1]
 
1577
        self.assertEqual({'parents': ['a@cset-0-1'],
 
1578
                          'sha1': '1e105886d62d510763e22885eec733b66f5f09bf',
 
1579
                          'storage_kind': 'mpdiff',
 
1580
                         }, metadata_2a)
 
1581
        metadata_2b = records[1][1]
 
1582
        self.assertEqual({'parents': ['a@cset-0-1'],
 
1583
                          'sha1': 'f03f12574bdb5ed2204c28636c98a8547544ccd8',
 
1584
                          'storage_kind': 'mpdiff',
 
1585
                         }, metadata_2b)
 
1586
        metadata_3 = records[2][1]
 
1587
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
 
1588
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1589
                          'storage_kind': 'mpdiff',
 
1590
                         }, metadata_3)
 
1591
        bytes_2a = records[0][0]
 
1592
        self.assertEqualDiff(
 
1593
            'i 1\n'
 
1594
            '<inventory format="10" revision_id="a@cset-0-2a">\n'
 
1595
            '\n'
 
1596
            'c 0 1 1 1\n'
 
1597
            'i 1\n'
 
1598
            '<file file_id="file-id" name="file" parent_id="root-id"'
 
1599
                ' revision="a@cset-0-2a"'
 
1600
                ' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
 
1601
                ' text_size="12" />\n'
 
1602
            '\n'
 
1603
            'c 0 3 3 1\n', bytes_2a)
 
1604
        bytes_2b = records[1][0]
 
1605
        self.assertEqualDiff(
 
1606
            'i 1\n'
 
1607
            '<inventory format="10" revision_id="a@cset-0-2b">\n'
 
1608
            '\n'
 
1609
            'c 0 1 1 2\n'
 
1610
            'i 1\n'
 
1611
            '<file file_id="file2-id" name="other-file" parent_id="root-id"'
 
1612
                ' revision="a@cset-0-2b"'
 
1613
                ' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
 
1614
                ' text_size="14" />\n'
 
1615
            '\n'
 
1616
            'c 0 3 4 1\n', bytes_2b)
 
1617
        bytes_3 = records[2][0]
 
1618
        self.assertEqualDiff(
 
1619
            'i 1\n'
 
1620
            '<inventory format="10" revision_id="a@cset-0-3">\n'
 
1621
            '\n'
 
1622
            'c 0 1 1 2\n'
 
1623
            'c 1 3 3 2\n', bytes_3)
 
1624
 
 
1625
    def test_creating_bundle_preserves_chk_pages(self):
 
1626
        self.make_merged_branch()
 
1627
        target = self.b1.bzrdir.sprout('target',
 
1628
                                       revision_id='a@cset-0-2a').open_branch()
 
1629
        bundle_txt, rev_ids = self.create_bundle_text('a@cset-0-2a',
 
1630
                                                      'a@cset-0-3')
 
1631
        self.assertEqual(['a@cset-0-2b', 'a@cset-0-3'], rev_ids)
 
1632
        bundle = read_bundle(bundle_txt)
 
1633
        target.lock_write()
 
1634
        self.addCleanup(target.unlock)
 
1635
        install_bundle(target.repository, bundle)
 
1636
        inv1 = self.b1.repository.inventories.get_record_stream([
 
1637
            ('a@cset-0-3',)], 'unordered',
 
1638
            True).next().get_bytes_as('fulltext')
 
1639
        inv2 = target.repository.inventories.get_record_stream([
 
1640
            ('a@cset-0-3',)], 'unordered',
 
1641
            True).next().get_bytes_as('fulltext')
 
1642
        self.assertEqualDiff(inv1, inv2)
1408
1643
 
1409
1644
 
1410
1645
class MungedBundleTester(object):
1459
1694
        self.check_valid(bundle)
1460
1695
 
1461
1696
 
1462
 
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
 
1697
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
1463
1698
 
1464
1699
    format = '0.9'
1465
1700
 
1497
1732
        self.check_valid(bundle)
1498
1733
 
1499
1734
 
1500
 
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
 
1735
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
1501
1736
 
1502
1737
    format = '4'
1503
1738
 
1504
1739
 
1505
 
class TestBundleWriterReader(TestCase):
 
1740
class TestBundleWriterReader(tests.TestCase):
1506
1741
 
1507
1742
    def test_roundtrip_record(self):
1508
1743
        fileobj = StringIO()
1573
1808
        self.assertRaises(errors.BadBundle, record_iter.next)
1574
1809
 
1575
1810
 
1576
 
class TestReadMergeableFromUrl(TestCaseWithTransport):
 
1811
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
1577
1812
 
1578
1813
    def test_read_mergeable_skips_local(self):
1579
1814
        """A local bundle named like the URL should not be read.
1585
1820
            def look_up(self, name, url):
1586
1821
                return 'source'
1587
1822
        directories.register('foo:', FooService, 'Testing directory service')
1588
 
        self.addCleanup(lambda: directories.remove('foo:'))
 
1823
        self.addCleanup(directories.remove, 'foo:')
1589
1824
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1590
1825
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1591
1826
                          'foo:bar')
1592
1827
 
 
1828
    def test_infinite_redirects_are_not_a_bundle(self):
 
1829
        """If a URL causes TooManyRedirections then NotABundle is raised.
 
1830
        """
 
1831
        from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
 
1832
        server = RedirectingMemoryServer()
 
1833
        self.start_server(server)
 
1834
        url = server.get_url() + 'infinite-loop'
 
1835
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
 
1836
 
1593
1837
    def test_smart_server_connection_reset(self):
1594
1838
        """If a smart server connection fails during the attempt to read a
1595
1839
        bundle, then the ConnectionReset error should be propagated.
1596
1840
        """
1597
1841
        # Instantiate a server that will provoke a ConnectionReset
1598
1842
        sock_server = _DisconnectingTCPServer()
1599
 
        sock_server.setUp()
1600
 
        self.addCleanup(sock_server.tearDown)
 
1843
        self.start_server(sock_server)
1601
1844
        # We don't really care what the url is since the server will close the
1602
1845
        # connection without interpreting it
1603
1846
        url = sock_server.get_url()
1607
1850
class _DisconnectingTCPServer(object):
1608
1851
    """A TCP server that immediately closes any connection made to it."""
1609
1852
 
1610
 
    def setUp(self):
 
1853
    def start_server(self):
1611
1854
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1612
1855
        self.sock.bind(('127.0.0.1', 0))
1613
1856
        self.sock.listen(1)
1625
1868
    def get_url(self):
1626
1869
        return 'bzr://127.0.0.1:%d/' % (self.port,)
1627
1870
 
1628
 
    def tearDown(self):
 
1871
    def stop_server(self):
1629
1872
        try:
1630
1873
            # make sure the thread dies by connecting to the listening socket,
1631
1874
            # just in case the test failed to do so.
1636
1879
            pass
1637
1880
        self.sock.close()
1638
1881
        self.thread.join()
1639